14 Commits
Author SHA1 Message Date
LinusandClaude Opus 5.5 c198c36ea4 Add unsquashfs and stop SquashFS tools failing on xattrs
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>
2026-11-02 19:51:45 +01:00
LinusandClaude Opus 5.5 0315d5816e Ask for the model provider on every start
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>
2026-11-02 18:54:41 +01:00
LinusandClaude Opus 5.5 9b921eeae5 Add install.sh with an app-menu launcher
install.sh builds the image (run.sh --build-only) and adds a "Claude RE
Workstation" desktop entry that starts this checkout's run.sh, with
Claude Desktop's icons taken from the image under their own name.
--uninstall removes the launcher and icons again.

run.sh gains --build-only, which needs no display, and shows errors in a
zenity dialog when it has no terminal, as when started from the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:35:29 +01:00
LinusandClaude Opus 5.5 ab9bb3eae9 Add unblob so REA can extract firmware
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>
2026-11-02 18:20:01 +01:00
LinusandClaude Opus 5.5 e61c004431 Install REA's skill into ~/.claude/skills in the image
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>
2026-11-02 18:10:35 +01:00
LinusandClaude Opus 5.5 43ed5f9183 Add the REA (Reverse Engineer Anything) MCP server
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>
2026-11-02 16:43:08 +01:00
LinusandClaude Opus 5.5 dbce497f95 Add binwalk, esptool and the ESP32 flash loader for Ghidra
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>
2026-11-02 14:02:22 +01:00
LinusandClaude Opus 5.5 ab6fc403e4 Install GhidraMCP 2.0 and connect it to Claude Desktop
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>
2026-11-02 13:12:59 +01:00
LinusandClaude Opus 5.5 51573544a4 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>
2026-11-02 12:58:19 +01:00
LinusandClaude Opus 5.5 705f4a52a6 Enable Ghidra's Developer plugin package in the CodeBrowser
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>
2026-11-02 12:45:58 +01:00
LinusandClaude Opus 5.5 4baf0208e1 Add Ghidra 12.2 and start it alongside Claude Desktop
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>
2026-11-02 12:38:38 +01:00
LinusandClaude Opus 5.5 16dbf25c9b Share a chosen workspace folder and stop with the window
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>
2026-11-02 12:22:38 +01:00
LinusandClaude Opus 5.5 f01fe47f16 Add Claude Desktop container with per-start sign-in
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>
2026-11-02 12:07:19 +01:00
LinusandClaude Opus 5.5 e0b8f0e8c7 Add CLAUDE.md describing the Docker-based RE setup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:16:01 +01:00