Really enable Ghidra's Developer plugins in both tools

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>
This commit is contained in:
2026-11-02 12:58:19 +01:00
co-authored by Claude Opus 5.5
parent 705f4a52a6
commit 51573544a4
5 changed files with 62 additions and 6 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ Early stage. What exists: a single image (`Dockerfile`, `launch-apps.sh`) with C
## Architecture
- **Claude Desktop and Ghidra share one container** (the root `Dockerfile`, started by `run.sh`), at the user's request. Ghidra is the official GitHub release (version, build date and SHA-256 are build args), runs on Ubuntu's `openjdk-25-jdk`, lives in `/opt/ghidra` and starts together with Claude Desktop. The CodeBrowser has the **Developer** plugin package enabled: the build patches `defaultTools/CodeBrowser.tool` inside `Public_Release.jar` (user settings reset every start, so patching user config wouldn't stick). Further services such as the RE MCP server may still get their own containers.
- **Claude Desktop and Ghidra share one container** (the root `Dockerfile`, started by `run.sh`), at the user's request. Ghidra is the official GitHub release (version, build date and SHA-256 are build args), runs on Ubuntu's `openjdk-25-jdk`, lives in `/opt/ghidra` and 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 inserts `ghidra/codebrowser-developer.xml` into `defaultTools/CodeBrowser.tool` inside `Public_Release.jar`. Project window: it has no default to patch, so `ghidra/FrontEndTool.xml` is copied into the user settings, along with `ghidra/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.sh` asks for the host folder on each start in the desktop's folder picker (`zenity`, or `kdialog` on KDE; terminal prompt if neither exists), opening at the last folder used, else `./workspace`. Skip it with `--workspace DIR` or `WORKSPACE=DIR`. It refuses `/` and `$HOME`. The image symlinks `~/workspace` to `/workspace` for 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 blanket `xhost +`. Scope it (`xhost +local:docker` or `+SI:localuser:...`).
- **GhidraMCP wiring.** GhidraMCP has two parts:
+22 -5
View File
@@ -56,21 +56,38 @@ ENV GHIDRA_INSTALL_DIR=/opt/ghidra
# CodeBrowser. Ghidra copies its default tools into the user's settings on
# first start, and settings are reset on every container start, so the
# default CodeBrowser.tool inside the jar is patched instead of user config.
# The build fails if the line could not be inserted.
# A bare <PACKAGE NAME="Developer" /> only adds the package's RELEASED
# plugins; the snippet also lists the STABLE ones. The build fails if the
# snippet could not be inserted.
COPY ghidra/codebrowser-developer.xml /tmp/codebrowser-developer.xml
RUN set -e; cd /tmp \
&& jar_file=/opt/ghidra/Ghidra/Configurations/Public_Release/lib/Public_Release.jar \
&& unzip -q "$jar_file" defaultTools/CodeBrowser.tool \
&& sed -i '0,/<ROOT_NODE/s//<PACKAGE NAME="Developer" \/>\n <ROOT_NODE/' defaultTools/CodeBrowser.tool \
&& grep -q '<PACKAGE NAME="Developer" />' defaultTools/CodeBrowser.tool \
&& awk 'FNR==NR { snip = snip $0 "\n"; next } !done && /<ROOT_NODE/ { printf "%s", snip; done = 1 } { print }' \
/tmp/codebrowser-developer.xml defaultTools/CodeBrowser.tool > /tmp/CodeBrowser.tool \
&& mv /tmp/CodeBrowser.tool defaultTools/CodeBrowser.tool \
&& grep -q 'FidDebugPlugin' defaultTools/CodeBrowser.tool \
&& jar uf "$jar_file" defaultTools/CodeBrowser.tool \
&& rm -r /tmp/defaultTools
&& rm -r /tmp/defaultTools /tmp/codebrowser-developer.xml
# Ubuntu 24.04 ships a default "ubuntu" user on UID 1000; replace it.
RUN if id ubuntu >/dev/null 2>&1; then userdel -r ubuntu; fi \
&& groupadd -g "${USER_GID}" "${USER_NAME}" \
&& useradd -m -u "${USER_UID}" -g "${USER_GID}" -s /bin/bash "${USER_NAME}" \
&& install -d -o "${USER_UID}" -g "${USER_GID}" /workspace \
&& ln -s /workspace "/home/${USER_NAME}/workspace"
&& ln -s /workspace "/home/${USER_NAME}/workspace" \
&& install -d -o "${USER_UID}" -g "${USER_GID}" \
"/home/${USER_NAME}/.config" \
"/home/${USER_NAME}/.config/ghidra" \
"/home/${USER_NAME}/.config/ghidra/ghidra_${GHIDRA_VERSION}_PUBLIC"
# Ghidra's project window has no default config to patch; it builds one in
# code and saves it on exit. So a starting FrontEndTool.xml with the Developer
# plugins is put in the user settings. Pre-creating the settings dir makes
# Ghidra ask for its user agreement, which it otherwise accepts by itself on
# a fresh start, so preferences carries the same USER_AGREEMENT=ACCEPT.
COPY --chown=${USER_UID}:${USER_GID} ghidra/FrontEndTool.xml ghidra/preferences \
/home/${USER_NAME}/.config/ghidra/ghidra_${GHIDRA_VERSION}_PUBLIC/
COPY --chmod=755 launch-apps.sh /usr/local/bin/launch-apps
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Initial config for Ghidra's project window (front end). Ghidra overwrites
it on exit; it is copied into the image so that every container start
opens with the "Developer" plugin package enabled (File > Configure). -->
<!-- VERSION must match GHIDRA_VERSION in the Dockerfile. -->
<FRONTEND VERSION="12.2">
<TOOL_CONFIG CONFIG_NAME="NO_LONGER_USED">
<ICON LOCATION="view_detailed.png" />
<TOOL TOOL_NAME="Ghidra: NO ACTIVE PROJECT" INSTANCE_NAME="">
<OPTIONS />
<PACKAGE NAME="Utility" />
<PACKAGE NAME="Ghidra Core" />
<!-- Developer plugins that may run in the project window. All are
STABLE, not RELEASED, so each must be listed explicitly. -->
<PACKAGE NAME="Developer">
<INCLUDE CLASS="ghidra.app.plugin.debug.DomainFolderChangesDisplayPlugin" />
<INCLUDE CLASS="ghidra.app.plugin.debug.GenerateOldLanguagePlugin" />
<INCLUDE CLASS="ghidra.app.plugin.debug.JavaHelpPlugin" />
</PACKAGE>
<ROOT_NODE X_POS="560" Y_POS="240" WIDTH="800" HEIGHT="600" EX_STATE="0" FOCUSED_OWNER="FrontEnd Tool" FOCUSED_NAME="FrontEnd" FOCUSED_TITLE="Project Window">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="FrontEnd" OWNER="FrontEnd Tool" TITLE="Project Window" ACTIVE="true" GROUP="Default" />
</COMPONENT_NODE>
</ROOT_NODE>
</TOOL>
</TOOL_CONFIG>
</FRONTEND>
+8
View File
@@ -0,0 +1,8 @@
<!-- Inserted at build time (see Dockerfile). The bare package adds its
RELEASED plugins; the STABLE ones must be listed explicitly. -->
<PACKAGE NAME="Developer">
<INCLUDE CLASS="ghidra.app.plugin.debug.GenerateOldLanguagePlugin" />
<INCLUDE CLASS="ghidra.app.plugin.debug.JavaHelpPlugin" />
<INCLUDE CLASS="ghidra.app.plugin.gui.WindowLocationPlugin" />
<INCLUDE CLASS="ghidra.feature.fid.plugin.FidDebugPlugin" />
</PACKAGE>
+4
View File
@@ -0,0 +1,4 @@
# Same value Ghidra writes on its own on a fresh start (no settings dir).
# Needed because the image pre-creates the settings dir for FrontEndTool.xml,
# which otherwise makes Ghidra show its user agreement dialog every start.
USER_AGREEMENT=ACCEPT