Skip to content

refactor: multi-stage Dockerfile, pinned tool versions, CI/release workflows#3

Merged
mitsuakki merged 18 commits into
mainfrom
dev
Jul 5, 2026
Merged

refactor: multi-stage Dockerfile, pinned tool versions, CI/release workflows#3
mitsuakki merged 18 commits into
mainfrom
dev

Conversation

@mitsuakki

Copy link
Copy Markdown
Owner

What

Split the single-stage Dockerfile into 7 stages with pinned tool versions,
CI validation, and a tag-driven release workflow.

Dockerfile

  • 7 stages: basebase-buildpython, r2-ghidra, fuzzing,
    androidfinal
  • base/build split: runtime deps in base, build toolchain (gcc, cmake,
    maven, autotools) in base-build. Final stage FROM base — those ~400MB
    never ship.
  • All versions pinned via ARGs: radare2 6.0.8, Ghidra 12.1.2,
    angr 9.2.194, AFL++ v5.00c, honggfuzz 2.5, apktool 2.10.0, jadx 1.5.1,
    frida-tools 14.9.0, platform-tools 37.0.0, ghidra-mcp v5.13.1.
    Bump one ARG, only that stage + final rebuilds.
  • Each builder stage installs to /opt/tools/<name> (prefixed, no
    /usr/local pollution). Final stage merges via COPY --from.

CI

  • .github/workflows/build.yml — triggers on push/PR to main, builds with
    BuildKit + GHA cache, push: false. Catches breakage before merge.
  • .github/workflows/release.yml — triggers on v*.*.* tag, builds +
    pushes to ghcr.io/<repo> with semver tags. Auth via GITHUB_TOKEN.

Docs

  • README now lists minimum Docker (23.0+) and Compose (2.0+) versions.
    Fallback for older Docker with DOCKER_BUILDKIT=1.

Impact

  • Rebuild time: changing one pip package rebuilds only python + final
    (~2 min instead of ~30 min full rebuild).
  • Image size: ~400MB smaller (build toolchain excluded from final).
  • Reproducibility: exact versions pinned. Same Dockerfile → same image.

mitsuakki added 6 commits July 2, 2026 18:50
- Rename Docker container from ctf-re to toolbox in all configs and docs
- Fix Ghidra headless MCP startup: build proper classpath with Ghidra
  runtime jars (Framework, Features, Processors, Debug), use
  com.xebyte.headless.GhidraMCPHeadlessServer entry point, bind to
  loopback only, add JVM flags (-Xmx4g -XX:+UseG1GC)
- Robust headless JAR discovery: check target/archive-tmp first, then
  target/*Headless*.jar, fall back to largest jar in target/
- Simplify ghidra-headless bridge config: pass GHIDRA_MCP_URL via env
  var instead of --ghidra-server CLI arg; remove ghidra-gui entry
  (GUI bridge belongs in user's local config, not base config)
…me split

Split single-stage Dockerfile into 7 stages:
- base: runtime OS packages only (no build toolchain), BinDiff
- base-build: extends base with gcc/cmake/maven/autotools (~400MB,
  discarded after build)
- python: angr, pwntools, frida, MCP stack (all pip packages pinned)
- r2-ghidra: radare2 + r2pm plugins, Ghidra headless, ghidra-mcp
- fuzzing: AFL++, honggfuzz (prefixed to /opt/tools/fuzzing)
- android: platform-tools, apktool, jadx
- final: FROM base, COPY --from each builder, scripts, configs

All tools pinned to exact versions via ARGs — builds are reproducible.
Bump a version, only that stage + final rebuilds.

Final image ~400MB smaller: gcc, cmake, maven, llvm-dev, autotools
never ship (they live only in base-build, discarded after build).
build.yml: triggers on push/PR to main, builds image with BuildKit +
GHA cache, push: false. Fails fast if Dockerfile breaks.

release.yml: triggers on tag push (v*.*.* / v*.*), builds + pushes to
ghcr.io with semver tags (major, major.minor, version). Uses GITHUB_TOKEN
for auth — no extra secrets needed.
Docker 23.0+ (BuildKit default), Compose 2.0+ (Compose V2).
Fallback for Docker 18.09-22.x: export DOCKER_BUILDKIT=1.
@mitsuakki mitsuakki self-assigned this Jul 5, 2026
@mitsuakki mitsuakki added the enhancement New feature or request label Jul 5, 2026
angr 9.2.194 pins capstone==5.0.3, unicorn==2.1.1, etc. — installing
capstone==5.0.5 alongside angr causes ResolutionImpossible.

Let pip resolve capstone, unicorn, claripy, pyelftools, z3-solver
(angr's transitive deps). Keep pins on top-level packages only.

Also fix typo: PWNYOOLS_VERSION -> PWNTOOLS_VERSION.
chatgpt-codex-connector[bot]

This comment was marked as resolved.

mitsuakki added 11 commits July 5, 2026 12:28
All modified files had CRLF from Windows mount. Renormalized to LF.
Added .gitattributes with '* text=auto eol=lf' + explicit rules for
.sh, .py, .json, .yml, Dockerfile, .md to prevent recurrence.
2.5 may have build issues on Ubuntu 24.04's toolchain.
Split RUN into build + install steps for clearer error attribution.
ghidra-mcp v5.13.1 dropped the --ghidra-server CLI flag. The bridge now
reads GHIDRA_MCP_URL from the environment instead. Updated all 3 README
examples to match configs/base.json.
Refactor removed the timeout wrapping — exec() ran unbounded. A blocking
analysis or while True: pass would hang the server until killed.

Run exec() via asyncio.to_thread wrapped in asyncio.wait_for(timeout).
On timeout: returns error, server stays responsive. Thread keeps running
(Python limitation) but MCP no longer hangs.
Binding to 127.0.0.1 inside the container rejects connections from host
port forwarding (Docker connects to container's non-loopback interface).
Bind to 0.0.0.0 so curl http://localhost:8089 works from the host
as documented in README, while internal bridge at 127.0.0.1:8089 still works.
… angr MCP

Drop custom angr-mcp, fuzz-mcp, python-mcp servers. angr ships its own
MCP (python -m angr.mcp). Shell MCP covers everything else. Remove
fuzz-init.sh and angr-solve.py CLI wrappers — direct CLI invocation
preferred. Cap shell-mcp timeout at 300s. Document angr built-in MCP
and C compile/run MCP in README.
…deps

- Keep radare2-src after sys/install.sh — binaries symlink to it
- Patch r2mcp: r_core_call_str_at → r_core_cmd_str_at for radare2 6.0+
- Add gcc/g++/make to base image for C MCP support
- Pin pycparser==2.22 for angr compatibility
- Set LD_LIBRARY_PATH and PKG_CONFIG_PATH for radare2
- Add r2pm prefix bin to PATH
- Rename service: ctf-re → re-toolbox
- Add GHIDRA_MCP_AUTH_TOKEN env var
Four-step workflow:
1. Health-check MCP server via /health endpoint
2. Import binary via analyzeHeadless (create project, auto-analyze)
3. Load program into MCP bridge via /load_program
4. Verify program is loaded

Supports --no-analyze for fast import, configurable via env vars:
GHIDRA_MCP_URL, GHIDRA_MCP_AUTH_TOKEN, GHIDRA_PROJECTS_DIR.
…g tools

Remove c-mcp.py section (file never created), angr-solve.py CLI examples
(script deleted), fuzz-init.sh references (script deleted). Add shell MCP
docs, angr built-in MCP config, load-ghidra.sh usage, project structure.
All MCP configs match actual state of configs/base.json and .mcp.json.
@mitsuakki
mitsuakki merged commit 3b59d3e into main Jul 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant