diff --git a/CLAUDE.md b/CLAUDE.md
index 84da884..6599774 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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 `` only adds the package's RELEASED plugins, so the STABLE ones are listed with ``. 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:
diff --git a/Dockerfile b/Dockerfile
index ba8bc15..8456e26 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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 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,/\n ' defaultTools/CodeBrowser.tool \
+ && awk 'FNR==NR { snip = snip $0 "\n"; next } !done && / /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
diff --git a/ghidra/FrontEndTool.xml b/ghidra/FrontEndTool.xml
new file mode 100644
index 0000000..99e4f5c
--- /dev/null
+++ b/ghidra/FrontEndTool.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ghidra/codebrowser-developer.xml b/ghidra/codebrowser-developer.xml
new file mode 100644
index 0000000..b563c45
--- /dev/null
+++ b/ghidra/codebrowser-developer.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/ghidra/preferences b/ghidra/preferences
new file mode 100644
index 0000000..54d432a
--- /dev/null
+++ b/ghidra/preferences
@@ -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