mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #73] [MERGED] feat(groom): isolated netns sandbox — drop --share-net, broker over a bind-mounted unix socket (BE-4421) #83
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#83
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/73
Author: @mattmillerai
Created: 7/24/2026
Status: ✅ Merged
Merged: 8/3/2026
Merged by: @mattmillerai
Base:
matt/be-4302-groom-sandbox← Head:matt/be-4421-groom-sandbox-netns📝 Commits (2)
c5f9300feat(groom): isolated netns sandbox — drop --share-net, broker over a bind-mounted unix socket (BE-4421)dc20444fix(groom): harden UDS broker/shim against review panel findings (BE-4421)📊 Changes
5 files changed (+281 additions, -47 deletions)
View changed files
📝
.github/groom/README.md(+31 -8)📝
.github/groom/agent-sandbox.sh(+52 -11)📝
.github/groom/broker.mjs(+68 -16)➕
.github/groom/jail-shim.mjs(+44 -0)📝
.github/groom/tests/sandbox-tests.sh(+86 -12)📄 Description
ELI-5
The groom auto-builder runs an untrusted agent in a locked box. Until now the box still had a working network cable, so a misbehaving agent could in principle phone home, hit the runner's own services, or read cloud metadata. This change cuts the cable entirely: the box gets its own empty network with only a loopback wire. The one thing the agent still needs — talking to Anthropic through the key-broker — comes in through a tiny door (a unix socket) we mount into the box, with a little in-box translator (
jail-shim.mjs) so ordinary HTTP tooling can use it. Everything else off-box is now unreachable by construction, and we prove it with tests that show cloud metadata, an arbitrary internet IP, and the host's own loopback all fail to connect from inside.What & why
Phase 2 of the groom sandbox hardening (design from spike BE-4369: namespace isolation by construction — option b; slirp4netns/nftables/broker-in-jail alternatives were rejected). Removes
--share-netso the jail runs in an isolated network namespace with onlyloup — no external hosts, no host-loopback services, no cloud metadata (169.254.169.254 / 168.63.129.16) — while keeping the key-broker reachable over a unix-domain socket bind-mounted into the jail.broker.mjs— now accepts a unix-socket path (absolute) asargv[2]in addition to the legacy numeric TCP port. UDS mode clears a stale socket thenserver.listen(path); a relative path is refused loud. The request-handling contract (strip/inject key,/v1/*only,/healthz, SSE, crash-proofing) is transport-independent and unchanged.jail-shim.mjs(new, ~20 lines, node stdlib) — a TCP→UDS forwarder run inside the jail (node jail-shim.mjs <port> /run/broker.sock) so agent tooling speaking HTTP to a127.0.0.1:<port>base URL reaches the bind-mounted socket (an isolated netns can't dial a host TCP port).agent-sandbox.sh— drops--share-netfrom both bwrap invocations (selftest+bwrap_args); adds--uds <host-socket-path>(at most once, absolute, must already be a listening socket) that rw-binds the socket file at the fixed in-jail path/run/broker.sock(connect(2)needs write on the inode;--ro-bindwould EROFS). Omit--udsfor a fully-offline jail.tests/sandbox-tests.sh— section 5 reworked onto the UDS+shim path (broker listens on a socket; in-jail shim bridges curl to it; all existing assertions unchanged). New section 7 = the BE-4369 acceptance proofs: host loopback, cloud metadata, and an arbitrary external IP (1.1.1.1) are all unreachable from the jail, plus--udsfail-loud on a nonexistent socket.README.md— isolated-netns contract row, UDS mode + shim, and the offline consequences (setCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1; in-jailgit fetch/npm installcannot work — everything must be in the clone before sandboxing).Testing
shellcheck -x .github/groom/agent-sandbox.sh .github/groom/tests/sandbox-tests.sh— clean.--share-netanywhere under.github/groom/.python3 -m unittest(groom ledger, untouched) — 48 passing (sanity).bash sandbox-tests.shrequires Linux + unprivileged user namespaces (bubblewrap), so — exactly like the phase-1 BE-4302 suite — it runs on thesandbox-testsCI job onubuntu-latest, not on the macOS dev host. To de-risk the transport-independent JS on the host I ran a smoke test proving the real composition end-to-end: broker on a UDS +jail-shim.mjsbridging TCP→UDS → key injected, caller dummy stripped,/healthz200, non-/v1404, SSE frames +[DONE]stream through; plus TCP back-compat, relative-path refusal, andjail-shimarg validation all behave. The netns egress assertions rest on the spike-verified facts (bubblewrap brings uploin the fresh netns; empty-netns connects fail immediateENETUNREACH).Negative-claim falsification (per the coder self-review)
This change's user-facing outcome denies a capability (network egress from the jail) — the trigger for the falsification check. It is not the #581 anti-pattern: cutting egress is the deliberate BE-4369 security goal, and the one capability the agent legitimately needs — reaching Anthropic — is preserved and empirically proven working via the sanctioned UDS+shim path (see the host smoke above: real key forwarded, SSE intact). The "unreachable" assertions target the security boundary (metadata / external IP / host loopback), which is deterministic by netns construction, not a mistaken claim that a needed feature is unavailable.
Out of scope
The groom caller workflow (epic BE-3870) that composes broker + shim + agent in production doesn't exist yet; this PR defines and test-proves the composition pattern the caller will copy.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
pathinput scopes an audit to one directory, without resetting the cadence clock (BE-4757) #95