binwalk: new binwalk-build stage adapted from binwalk's own Dockerfile
to Ubuntu 24.04, with every source pinned: binwalk master (3.1.1),
Rust 1.99.1, dumpifs/lzfse/dmg2img commits, and the Python extractors
(jefferson, ubi-reader, uefi_firmware, vmlinux-to-elf, lz4, zstandard)
in /opt/binwalk-py. The final image adds upstream's apt extractors,
sasquatch (SHA-256 pinned), and Kaleido via KALEIDO_PATH so that
binwalk -E -p works (binwalk's build otherwise leaves it in /root of the
build stage and -p panics). BINWALK_RM_EXTRACTION_SYMLINK=1 avoids
dangling links to container paths.
esptool 5.6.0 in /opt/esptool. Entry points of both venvs are linked
into /usr/local/bin without shadowing the system python3.
ESP32 flash loader: dynacylabs/ghidra-esp32-flash-loader, built in an
esp32-loader-build stage against Ghidra 12.1.4 (no binaries are
published, and the older forks don't compile against Ghidra 12),
pinned to a master commit with its svd and ghidra_scripts submodules,
Gradle checked against gradle.org's SHA-256. Installed into
Ghidra/Extensions/esp32. Ghidra itself is now downloaded once in a
ghidra-dist stage and copied into the final image and the build stage.
Verified: binwalk extracted squashfs/gzip/cpio/7z from a test image and
rendered an entropy PNG; the loader imported a test ESP32 app image and
a flash dump with bootloader at 0x0 (partition table, bootloader and
factory app, SVD peripherals, ROM symbols). Original-ESP32 dumps with
the bootloader at 0x1000 are not supported; documented in CLAUDE.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Switch Ghidra to 12.1.4 (with openjdk-21-jdk), the version the
GhidraMCP 2.0 release is built for; Ghidra only loads extensions built
for its exact version.
From LaurieWired's GhidraMCP 2.0 release zip (SHA-256 checked):
- unpack the Ghidra extension into /opt/ghidra/Ghidra/Extensions; the
build fails if it doesn't target GHIDRA_VERSION. GhidraMCPPlugin is a
RELEASED Developer plugin, so the CodeBrowser config loads it, and it
serves its HTTP backend on 127.0.0.1:8080.
- install the ghidra-mcp wheel into /opt/ghidra-mcp, with every
dependency pinned to GhidraMCP's uv.lock at the release commit
(ghidra/uvlock-to-constraints.py, honouring per-Python markers).
claude-desktop/claude_desktop_config.json adds a stdio "ghidra" server
running ghidra-mcp against http://127.0.0.1:8080/ and is copied into
~/.config/Claude.
Verified with a test project: ghidra-mcp started from that config
connected to Ghidra 12.1.4 in GUI mode and returned program info,
segments and imports for the open program.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Ghidra installs its default tools into the user settings on first
start, and those settings are reset on every container start. So the
build patches defaultTools/CodeBrowser.tool inside Public_Release.jar
to include <PACKAGE NAME="Developer" />, and fails if the line could
not be inserted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Install Ghidra 12.2 from the official GitHub release into /opt/ghidra,
verified against the SHA-256 in the release notes, with Ubuntu's
openjdk-25-jdk (Ghidra 12.2 requires Java 25). Version, build date and
hash are build args.
The entrypoint, renamed from launch-claude.sh to launch-apps.sh, now
starts both apps and stops both, ending the container, when either is
closed: Ghidra when its process exits (closing the project window),
Claude Desktop when none of its windows is open or minimized. Claude is
stopped via its main process so it can shut down its own helpers.
CLAUDE.md now records that Claude Desktop and Ghidra share one
container, at the user's request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run.sh asks which host folder Claude may access, using the desktop's
folder picker (zenity, or kdialog on KDE; terminal prompt otherwise),
and mounts it read-write at /workspace. It opens at the last folder
used, refuses / and $HOME, and can be skipped with --workspace DIR.
The image creates /workspace and a ~/workspace symlink to it.
Closing Claude Desktop's window only hides the app, which left the
container running invisibly. The new entrypoint launch-claude.sh
watches the app's X windows and quits it once none is open or
minimized, so the container exits with it. run.sh adds --init for
signal handling and reaping.
Also add .gitignore for workspace/ and .env.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dockerfile installs Claude Desktop from Anthropic's apt repo (key
fingerprint verified) and runs it as a non-root user matching the host
UID/GID. run.sh forwards the host X display with a scoped xauth cookie
and checks that a window can be drawn before starting the app.
No keyring and no config volume: Electron uses --password-store=basic,
the container runs with --rm, so the user logs in on every start.
Chromium's sandbox can't create namespaces under Docker's default
seccomp profile, so the app runs with --no-sandbox; the container's own
seccomp and namespaces remain the boundary to the host.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>