mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #293] [MERGED] feat(groom): run the finder/verifier/builder agents inside the sandbox + key broker #293
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#293
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/293
Author: @mattmillerai
Created: 9/16/2026
Status: ✅ Merged
Merged: 9/16/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-4303-groom-sandbox-wire📝 Commits (3)
ad1036dfeat(groom): run the finder/verifier/builder agents inside the sandbox + key brokereff84d9fix(groom): address cursor-review panel findings on the sandbox+broker wiring4e80b8eMerge origin/main into matt/be-4303-groom-sandbox-wire📊 Changes
4 files changed (+547 additions, -410 deletions)
View changed files
📝
.github/groom/README.md(+36 -13)📝
.github/groom/agent-sandbox.sh(+10 -4)📝
.github/groom/tests/test_environment_binding.py(+120 -0)📝
.github/workflows/groom.yml(+381 -393)📄 Description
ELI-5
Groom runs three AI agents (a finder, a verifier, and a builder) over other
repos' code. That code is untrusted — a malicious repo could try to trick an
agent into stealing the Anthropic API key. Until now each agent step held the
real key in its environment and relied on a hand-rolled
chmod/env -uscrub.This PR moves all three agents inside the bubblewrap jail that phases 1–2
already built (
agent-sandbox.sh+broker.mjs+jail-shim.mjs). The real keynow lives only in a small "broker" step; the agent runs with a dummy key and
reaches Anthropic through the broker over a bind-mounted unix socket. The jail
also hides the runner's filesystem, clears the environment, and cuts off the
network — so a prompt-injected agent has nothing to steal and nowhere to send it.
This is the gate that had been blocking groom on untrusted-contributor repos.
What changed (per agent job:
audit_find,audit_verify, eachbuildcell)Start the key brokerstep — the only agent-facing step holdingsecrets.ANTHROPIC_API_KEY. Startsbroker.mjson the host socket$BROKER_SOCK, polls/healthz(fails the job after ~10s), records the pid.Run finder/Run verifier/Run builder) drops the keyand runs
agent-sandbox.sh— clonerofor finder/verifier,rw-git-roforthe builder. A
bash -cwrapper starts the in-jailjail-shim.mjsforwarder,waits for it, then
execs the pinnedclaudeCLI with a dummy key andANTHROPIC_BASE_URLpointed at the shim. The exec JSON is captured by ahost-side stdout redirect, out of the agent's reach.
GROOM_OUT_DIR(/tmp/groom-out) — the jail's solerw
--out-dir. Briefs and the builder's finding JSON stay at/tmpand arepassed
--ro-file. The verifier's finder-candidates download follows intoGROOM_OUT_DIRso the rw bind makes it readable in the jail.always()cleanup step kills the broker by recorded pid.chmod -R a-wclone/.gitlock+unlockdance, the
env -u GITHUB_*/RUNNER_TEMPprefix (--clearenvcovers it), andthe
rm -rfdiag pre-delete (host/tmpis a shadowed tmpfs inside the jail).own finder/verifier steps that hold the key with no agent present; the builder's
stays in
Capture patch), the type-guarded diag projection, the CLI pin/flags.test_environment_binding.py): asserts the real key isin no agent step and each agent step runs inside
agent-sandbox.shwith thedummy key — mirroring the existing bot-App-key boundary test. (Falsified: it
fails if the key is re-added to an agent step.)
Deviation from the ticket's plan — worth a careful look
The ticket described a TCP broker (
node broker.mjs 8082,ANTHROPIC_BASE_URL=http://127.0.0.1:8082, no netns). Since that plan waswritten, phase-2 hardening (BE-4421) evolved the merged assets to an isolated
network namespace with no egress, reaching the broker only over a
bind-mounted unix socket (
/run/broker.sock) bridged by the in-jailjail-shim.mjs. So the TCP recipe would not work against today'sagent-sandbox.sh.I implemented the ticket's intent using the canonical composition the repo
already documents and CI-tests —
sandbox-tests.sh§5 calls it "exactly thecomposition the production groom caller will use" (host
broker.mjson a UDS +agent-sandbox.sh --uds+ in-jailjail-shim.mjs). TheANTHROPIC_BASE_URLstill points at
http://127.0.0.1:8082, but 8082 is now the jail-local shimport, not a host TCP port.
One gap the ticket's plan omitted: the builder also writes
PR_BODY_OUT(BE-4346) and reads
FINDING_IN.PR_BODY_OUTmoved underGROOM_OUT_DIR(agent-written, needs the rw bind);
FINDING_INis passed--ro-file(read-onlyinput).
Acceptance proof
grep:secrets.ANTHROPIC_API_KEYappears in noRun finder/verifier/builderstep — only in the broker steps, the two scan steps, and
Capture patch(allno-agent). Verified by script and by the new unit test.
env -uprefix,rm -rfpre-delete).383 passed),check_workflow_pins.pyOK,check_agents_md.pyOK,shellcheck -x agent-sandbox.sh sandbox-tests.shOK,and
bash -n+shellcheckclean on every changedrun:script.Validation I could run locally
sandbox-tests.shsections 1–4 & 7 pass here (env scrub, FS confinement + tmpfsshadowing,
rw-git-ro, out-dir/clone overlap rejection, pid isolation, egressisolation).
broker.mjson a UDS +/healthzpoll +agent-sandbox.sh --uds+ in-jailjail-shim.mjs. Broker readiness over thesocket worked; the jail environ contained only the dummy key (
env→ANTHROPIC_API_KEY=groom-sandbox-placeholderplusHOME/PATH/PWD/SHLVL/TERM,no real key) — the ticket's negative proof, satisfied.
tr '\0' '\n' < /proc/self/environreturnsempty inside the jail (a
/proc/selfself-resolution/exec-timing quirk),so the robust command is
envor/proc/$$/environ.Residual — not exercised here; please complete before merge
workflow_dispatchgroomwith
dry_run: trueon the pilot caller (comfy-cloud-mcp-server), confirmingfinder/verifier/builder complete within
--max-turns, no newdenials_by_toolstorm, finder JSON parses, and a builder cell produces a patch. That needs
GitHub Actions + the real
ANTHROPIC_API_KEY+ a pilot repo, which a one-shotcannot do. Run it and paste the link before merging — it is an acceptance
criterion.
sandbox-tests.sh§5/§6 could not run on this dev host. This host hasnodeat a custom path outside/usr, so it is not inside the jail's read-only/usrbind and the shim/broker sections fail withnode: command not found.This reproduces identically on
origin/main's unmodified assets (pre-existing,host-only). On
ubuntu-latestnodelives under/usr, so those sections —and the same in-jail node+claude flow this PR relies on — pass in CI. Confirm
test-groom-scripts.ymlis green on CI as the second half of §5's proof.nodeand thenpm -g-installedclauderesolve inside the jail from/usr//usr/local(true on
ubuntu-latest; the CLI pin is untouched). If a self-hosted runnerput them elsewhere the jail would not see them — surfaced by the broker/shim
readiness poll failing the job loudly (fail-closed).
Provenance
PR_BODY_OUTunder the out-dir, which the ticket's file list omitted.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.