[GH-ISSUE #4588] [Bug] Linux Ghidra bridge cannot start where the sandbox refuses socket(AF_UNIX) #2381

Open
opened 2026-10-17 21:03:57 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @CameronBrooks11 on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4588

Area

Ghidra provider

Observed behavior

Inside Claude Code's Linux sandbox (bubblewrap plus a seccomp filter that returns EPERM for socket(AF_UNIX)), every Ghidra operation fails after import and auto-analysis complete:

$ TMPDIR=/tmp/claude GHIDRA_INSTALL_DIR=<ghidra_12.1.4_PUBLIC> \
  rea decompile ./t add_magic --provider ghidra --json
code: execution_failure
failure_kind: process
failure_message: Ghidra exited before bridge startup
transport: unix-socket
ERROR REPORT SCRIPT ERROR: (HeadlessAnalyzer) java.net.SocketException: Operation not permitted
    at java.base/sun.nio.ch.UnixDomainSockets.socket0(Native Method)
    at java.base/sun.nio.ch.ServerSocketChannelImpl... ServerSocketChannel.open(ServerSocketChannel.java:176)
    at ReaGhidraBridge.serveUnixSocket(ReaGhidraBridge.java:264)
    at ReaGhidraBridge.serve(ReaGhidraBridge.java:251)

src/ghidra/GhidraProviderClient.ts selects authenticated-loopback-tcp only when installation.platform === "win32"; every POSIX host gets unix-socket with no way to choose otherwise. In the same sandbox, a 127.0.0.1 TCP listener accepts a connection from the same command, and the Windows loopback path in ReaGhidraBridge.serveLoopbackTcp and GhidraTransport.observeGhidraEndpoint already has non-Windows branches.

The Hopper bridge has the same dependency (bridge/hopper_bridge.py binds AF_UNIX); Cutter already uses loopback TCP.

Reproduction

rea-agents 6.5.0 and main 61e688070, Linux x86-64 (Debian 13, kernel 6.12), Node 24.21.0, Ghidra 12.1.4, OpenJDK 21.0.12.1. Target: int add_magic(int x){ return x * 3 + 42; } compiled with gcc. Run the command above inside a sandbox whose seccomp filter denies socket(AF_UNIX). TMPDIR is set to a writable, dot-free path because ghidraSessionRoot() otherwise falls back to /tmp, which the sandbox mounts read-only; that is a separate issue.

Expected behavior

A POSIX host can select the existing authenticated loopback transport, as Windows does, and the provider states what that transport withholds. With the change in the linked PR (REA_GHIDRA_BRIDGE_TRANSPORT=authenticated-loopback-tcp) the same command returns:

int add_magic(int param_1)
{
  return param_1 * 3 + 0x2a;
}

with the transport named in limitations, and annotate-native-function returns capability_unavailable with the reason The authenticated loopback bridge transport does not admit database mutation; unset REA_GHIDRA_BRIDGE_TRANSPORT to use the Unix socket transport.

Open design question for the maintainers

The bridge keys mutation authority on the transport (ReaGhidraBridge.java 462-469, 504; GhidraClient.ts 888) as a proxy for the Windows P0 boundary; nothing about loopback TCP itself prevents set_address_name or annotate_native_function. The difference from the socket file is reachability: any local uid can connect to 127.0.0.1, and the per-session 256-bit token is the only authentication. Two ways to give a sandboxed POSIX session the full method set:

  1. Admit mutation on POSIX loopback: change the two Java gates and loopbackTransportCapabilities to key on a descriptor flag rather than the transport. Small, keeps one transport, accepts loopback reachability for mutation.
  2. A POSIX FIFO transport: two mkfifo -m 600 pipes in the 0700 runtime root, same newline-JSON framing and token line. A prototype inside the sandbox ran the handshake, an unauthenticated refusal, 200 KB and 300 KB frames in both directions, shutdown and cleanup; Java opens the request pipe for read then the response pipe for write, Node probes readiness with open(O_WRONLY|O_NONBLOCK) until ENXIO clears and opens the response pipe O_RDWR so EOF cannot precede the writer. It keeps the owner-only file boundary but needs mkfifo(1), a second descriptor in GhidraClient, FIFO branches in endpoint allocation, observation and connection, and liveness from process-exit observation instead of socket close.

Per CONTRIBUTING, neither is started here without a scope decision. The opt-in in the linked PR is read-only and needs none.

REA version

6.5.0; main 61e688070.

Runtime and platform

Linux x86-64, Node 24.21.0, Ghidra 12.1.4, JDK 21.0.12.1, Claude Code sandbox (bubblewrap + seccomp).

Interface

CLI and MCP (shared provider client).

Typed outcome or recovery expected

With the setting: the read-only operations succeed, mutation requests fail as capability_unavailable naming the setting. Without it: unchanged.

Originally created by @CameronBrooks11 on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4588 ### Area Ghidra provider ### Observed behavior Inside Claude Code's Linux sandbox (bubblewrap plus a seccomp filter that returns EPERM for `socket(AF_UNIX)`), every Ghidra operation fails after import and auto-analysis complete: ``` $ TMPDIR=/tmp/claude GHIDRA_INSTALL_DIR=<ghidra_12.1.4_PUBLIC> \ rea decompile ./t add_magic --provider ghidra --json code: execution_failure failure_kind: process failure_message: Ghidra exited before bridge startup transport: unix-socket ERROR REPORT SCRIPT ERROR: (HeadlessAnalyzer) java.net.SocketException: Operation not permitted at java.base/sun.nio.ch.UnixDomainSockets.socket0(Native Method) at java.base/sun.nio.ch.ServerSocketChannelImpl... ServerSocketChannel.open(ServerSocketChannel.java:176) at ReaGhidraBridge.serveUnixSocket(ReaGhidraBridge.java:264) at ReaGhidraBridge.serve(ReaGhidraBridge.java:251) ``` `src/ghidra/GhidraProviderClient.ts` selects `authenticated-loopback-tcp` only when `installation.platform === "win32"`; every POSIX host gets `unix-socket` with no way to choose otherwise. In the same sandbox, a `127.0.0.1` TCP listener accepts a connection from the same command, and the Windows loopback path in `ReaGhidraBridge.serveLoopbackTcp` and `GhidraTransport.observeGhidraEndpoint` already has non-Windows branches. The Hopper bridge has the same dependency (`bridge/hopper_bridge.py` binds `AF_UNIX`); Cutter already uses loopback TCP. ### Reproduction rea-agents 6.5.0 and main 61e688070, Linux x86-64 (Debian 13, kernel 6.12), Node 24.21.0, Ghidra 12.1.4, OpenJDK 21.0.12.1. Target: `int add_magic(int x){ return x * 3 + 42; }` compiled with gcc. Run the command above inside a sandbox whose seccomp filter denies `socket(AF_UNIX)`. `TMPDIR` is set to a writable, dot-free path because `ghidraSessionRoot()` otherwise falls back to `/tmp`, which the sandbox mounts read-only; that is a separate issue. ### Expected behavior A POSIX host can select the existing authenticated loopback transport, as Windows does, and the provider states what that transport withholds. With the change in the linked PR (`REA_GHIDRA_BRIDGE_TRANSPORT=authenticated-loopback-tcp`) the same command returns: ``` int add_magic(int param_1) { return param_1 * 3 + 0x2a; } ``` with the transport named in `limitations`, and `annotate-native-function` returns `capability_unavailable` with the reason `The authenticated loopback bridge transport does not admit database mutation; unset REA_GHIDRA_BRIDGE_TRANSPORT to use the Unix socket transport.` ### Open design question for the maintainers The bridge keys mutation authority on the transport (`ReaGhidraBridge.java` 462-469, 504; `GhidraClient.ts` 888) as a proxy for the Windows P0 boundary; nothing about loopback TCP itself prevents `set_address_name` or `annotate_native_function`. The difference from the socket file is reachability: any local uid can connect to `127.0.0.1`, and the per-session 256-bit token is the only authentication. Two ways to give a sandboxed POSIX session the full method set: 1. Admit mutation on POSIX loopback: change the two Java gates and `loopbackTransportCapabilities` to key on a descriptor flag rather than the transport. Small, keeps one transport, accepts loopback reachability for mutation. 2. A POSIX FIFO transport: two `mkfifo -m 600` pipes in the 0700 runtime root, same newline-JSON framing and token line. A prototype inside the sandbox ran the handshake, an unauthenticated refusal, 200 KB and 300 KB frames in both directions, shutdown and cleanup; Java opens the request pipe for read then the response pipe for write, Node probes readiness with `open(O_WRONLY|O_NONBLOCK)` until `ENXIO` clears and opens the response pipe `O_RDWR` so EOF cannot precede the writer. It keeps the owner-only file boundary but needs `mkfifo(1)`, a second descriptor in `GhidraClient`, FIFO branches in endpoint allocation, observation and connection, and liveness from process-exit observation instead of socket close. Per CONTRIBUTING, neither is started here without a scope decision. The opt-in in the linked PR is read-only and needs none. ### REA version 6.5.0; main 61e688070. ### Runtime and platform Linux x86-64, Node 24.21.0, Ghidra 12.1.4, JDK 21.0.12.1, Claude Code sandbox (bubblewrap + seccomp). ### Interface CLI and MCP (shared provider client). ### Typed outcome or recovery expected With the setting: the read-only operations succeed, mutation requests fail as `capability_unavailable` naming the setting. Without it: unchanged.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#2381
No description provided.