A bare <PACKAGE NAME="Developer" /> only adds the package's RELEASED plugins, so half of them (the STABLE ones) were missing and Configure showed Developer unticked. The project window wasn't configured at all. - CodeBrowser: insert ghidra/codebrowser-developer.xml, which lists the STABLE plugins explicitly, into the default CodeBrowser.tool in Public_Release.jar. - Project window: it has no default config to patch, so ship ghidra/FrontEndTool.xml in the user settings with its three Developer plugins. - Pre-creating the settings dir makes Ghidra show its user agreement; ghidra/preferences sets USER_AGREEMENT=ACCEPT, the value Ghidra writes by itself on a fresh start. Verified by closing Ghidra cleanly: it rewrites FrontEndTool.xml from the loaded plugins and kept all three Developer plugins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8.0 KiB
CLAUDE.md
Guidance for Claude Code when working in this repository.
Purpose
This repo provides a reproducible, Docker-managed reverse-engineering workstation on Linux, built around Claude Desktop with two MCP integrations:
- Reverse Engineer Anything MCP: general-purpose RE tooling exposed to Claude over MCP.
- Ghidra + GhidraMCP: Ghidra with the GhidraMCP plugin, so Claude can query and annotate an open program (functions, decompilation, xrefs, renames, comments).
The goal is that docker compose up gives you a working setup where Claude Desktop can drive both toolsets against binaries in a shared workspace, without installing Ghidra, a JDK or RE tooling on the host.
Status
Early stage. What exists: a single image (Dockerfile, launch-apps.sh) with Claude Desktop and Ghidra, started by run.sh; no compose setup yet. The layout below is the intended structure. Update this file when the real structure differs.
Intended layout
.
├── CLAUDE.md
├── README.md # user-facing setup and usage
├── docker-compose.yml # orchestrates all services
├── .env.example # versions, ports, UID/GID, display settings
├── claude-desktop/
│ ├── Dockerfile # Claude Desktop (Linux build) + GUI deps
│ └── claude_desktop_config.json # MCP server definitions (template)
├── ghidra/
│ ├── Dockerfile # JDK + Ghidra + GhidraMCP plugin
│ └── scripts/ # plugin install / headless helpers
├── re-mcp/
│ └── Dockerfile # Reverse Engineer Anything MCP server
├── scripts/ # host helpers (xhost setup, start/stop, health checks)
└── workspace/ # shared, git-ignored: binaries + Ghidra projects
Architecture
-
Claude Desktop and Ghidra share one container (the root
Dockerfile, started byrun.sh), at the user's request. Ghidra is the official GitHub release (version, build date and SHA-256 are build args), runs on Ubuntu'sopenjdk-25-jdk, lives in/opt/ghidraand starts together with Claude Desktop. The Developer plugin package is enabled in both the project window and the CodeBrowser. A bare<PACKAGE NAME="Developer" />only adds the package's RELEASED plugins, so the STABLE ones are listed with<INCLUDE CLASS=...>. CodeBrowser: the build insertsghidra/codebrowser-developer.xmlintodefaultTools/CodeBrowser.toolinsidePublic_Release.jar. Project window: it has no default to patch, soghidra/FrontEndTool.xmlis copied into the user settings, along withghidra/preferences(USER_AGREEMENT=ACCEPT, because a pre-created settings dir otherwise makes Ghidra ask for its user agreement). To check a config really loaded, close Ghidra cleanly and see whether the plugins survive in the file it rewrites; Ghidra drops what it didn't load. -
Shared workspace volume. Every container mounts the workspace at
/workspace, so a file path means the same thing to Claude, Ghidra and the RE MCP.run.shasks for the host folder on each start in the desktop's folder picker (zenity, orkdialogon KDE; terminal prompt if neither exists), opening at the last folder used, else./workspace. Skip it with--workspace DIRorWORKSPACE=DIR. It refuses/and$HOME. The image symlinks~/workspaceto/workspacefor Claude Desktop's file picker. -
GUI forwarding. Claude Desktop and Ghidra are GUI apps. Default to X11 forwarding (
/tmp/.X11-unix+DISPLAY). Support Wayland through XWayland. Keep a noVNC/VNC fallback in mind for headless hosts. Never solve display access with a blanketxhost +. Scope it (xhost +local:dockeror+SI:localuser:...). -
GhidraMCP wiring. GhidraMCP has two parts:
- a Ghidra plugin that runs an HTTP server inside Ghidra (default port
8080), and - a Python MCP bridge (
bridge_mcp_ghidra.py) that speaks MCP to Claude and forwards calls to that HTTP server.
The bridge is configured with the Ghidra server URL (e.g.
http://ghidra:8080/). The plugin must be enabled in the CodeBrowser and a program must be open, or the bridge has nothing to talk to. - a Ghidra plugin that runs an HTTP server inside Ghidra (default port
-
Container lifetime follows the apps. The entrypoint
launch-apps.shstarts Claude Desktop and Ghidra and stops both, ending the container, when either is closed. Ghidra counts as closed when its process exits (closing the project window quits it; CodeBrowser or Help windows don't). Claude Desktop's window only hides the app on close (GNOME shows no tray icon), so its X windows are watched: none open or minimized for ~3 s counts as closed.run.shuses--initso signals and zombie processes are handled. -
MCP transport between containers. Claude Desktop starts local MCP servers as stdio subprocesses. Because the servers live in other containers, pick one approach and apply it consistently:
- Preferred: run the MCP servers with an HTTP/SSE transport in their own containers. Bridge them into Claude Desktop's stdio config with a small proxy (e.g.
mcp-remoteormcp-proxy) installed in the Claude Desktop image. - Alternative: install the bridges inside the Claude Desktop image and point them at the other containers over the network. This works well for the GhidraMCP bridge, which is just Python + HTTP.
- Avoid mounting the Docker socket into the Claude Desktop container just to
docker execMCP servers.
- Preferred: run the MCP servers with an HTTP/SSE transport in their own containers. Bridge them into Claude Desktop's stdio config with a small proxy (e.g.
Conventions
- Pin versions. Ghidra version, GhidraMCP release, JDK version, Claude Desktop build and RE MCP commit go in
.env/ build args, notlatest. The GhidraMCP release must match the Ghidra version it was built for. - Run as non-root inside containers. Map the host UID/GID (via
.env) so files inworkspace/stay owned by the user. - Persist state in named volumes or
workspace/: Ghidra user settings and projects. Rebuilding an image must not lose analysis. - Claude Desktop sign-in is not persisted. No keyring is installed and
~/.config/Claudeis not mounted, so the user logs in on every start. Don't add a keyring or config volume back without being asked. - No secrets in the repo. Credentials, session data and
workspace/contents are git-ignored. Ship.env.example, never.env. - Untrusted binaries. Samples under analysis may be malicious. Never execute them on the host or in the Claude Desktop container. Containers that might run samples (dynamic analysis) should have no host mounts beyond
workspace/and should allow network isolation (network_mode: noneor an internal-only network). - Linux only. Target recent Docker Engine + Compose v2 (
docker compose, notdocker-compose). Make the setup work under both Docker and rootless Podman where practical. On SELinux hosts such as Fedora, bind mounts need:z/:Z.
Common commands
These are expected once the compose setup exists. Keep this list in sync with reality.
cp .env.example .env # then adjust UID/GID, versions, DISPLAY
xhost +local:docker # allow containers to use the X server
docker compose build
docker compose up -d
docker compose logs -f <service>
docker compose down
Things to verify, not assume
- Claude Desktop on Linux. Check whether an official Linux build is available at the time of work. If not, use a maintained community repackaging and document which one and why. Confirm the config path, usually
~/.config/Claude/claude_desktop_config.json. - Reverse Engineer Anything MCP. Confirm the upstream repository, install method, runtime (Python/Node), required external tools and supported transports before writing its Dockerfile. Record the source URL in the README.
- GhidraMCP. Confirm the current release, its supported Ghidra version, the bridge's CLI flags and its default ports from the upstream README.
When making changes
- After changing a Dockerfile or compose file, run
docker compose configto validate it, then build the affected service. - When adding an MCP server, update
claude-desktop/claude_desktop_config.json,docker-compose.yml,README.mdand the Architecture section above together.