Install Ubuntu's squashfs-tools so the stock unsquashfs/mksquashfs exist
next to sasquatch. As the non-root container user, unsquashfs and
sasquatch can't restore security.* xattrs and exit 2 although every file
was extracted; wrap them to pass -no-xattrs unless an xattr option is
given.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run.sh shows a window (zenity form, terminal prompts without zenity) for
a base URL, token, auth header and optional model list. Left empty,
Claude Desktop starts normally with the claude.ai sign-in. Otherwise it
runs in its third-party gateway mode: run.sh writes the managed config
(inferenceProvider "gateway" and friends) and copies it into the created
container as root, because Claude Desktop ignores
/etc/claude-desktop/managed-settings.json unless root owns it. The host
copy is deleted right away; nothing is kept between starts.
Claude Desktop only allows plain http to loopback, so for an http://
gateway launch-apps.sh runs socat on 127.0.0.1:18080 and forwards to it.
localhost is rewritten to host.docker.internal for servers on the host.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
unblob 26.6.4 (the version REA is verified with) is installed from its
PyPI wheel into /opt/unblob, with dependencies pinned to its uv.lock at
the release commit. A wrapper puts that venv first on PATH, and
REA_UNBLOB_COMMAND points REA's extract_firmware at it.
Its extractors follow upstream's install-deps.sh: Ubuntu packages,
sasquatch v4.5.1-6 (also used by binwalk) and onekey-sec's e2fsprogs
fork, whose debugfs rdump options the ext2/3/4 handler needs. The build
checks that unblob finds every extractor.
The uv.lock converter moves to scripts/ and skips the locked project
itself generically, since ghidra-mcp and unblob both use it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude Code sessions in Claude Desktop's Code tab load user skills from
~/.claude/skills, so the reverse-engineer-anything skill is copied there
from the installed rea-agents package and stays in step with REA's
version. Claude Desktop's chat doesn't read that folder; its skills come
from the account.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
REA 6.8.0 (rea-agents on npm, checked against the registry's integrity
hash) runs in its own container from the same image, because some of its
tools run the target: workspace only, no display, no capabilities, and
only an internal network with no route out. It serves MCP over HTTP and
Claude Desktop reaches it through mcp-remote, installed from a committed
lockfile. It uses the image's Ghidra 12.1.4 headless and binwalk.
run.sh creates the internal network, starts REA before Claude Desktop and
stops it when Claude Desktop's container ends. Adds a README with usage
and sources.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>