P0 security: contain the implement agent against untrusted issue text#9
Merged
Conversation
…trip tokens improvement-plan 0.2-1 (a)+(c). The implement/revise agent acts on untrusted issue text with broad Bash and a PAT in env — a prompt-injection RCE/exfil vector on a public repo. (a) Network egress denied by default (claude harness): add --disallowed-tools for curl/wget/nc/ncat/netcat/telnet/ssh/scp/sftp/socat/ftp + mcp__*. Deny beats the broad Bash allow (Claude evaluates deny first) and survives bash -c / && / ; / | wrappers, so "curl $URL", "bash -c curl", and "git x && curl y" are all blocked. Opt back in with .implement.allow_network=true (new schema key). (c) Strip GH_TOKEN/GITHUB_TOKEN/AGENT_GH_TOKEN/ADMIN_GH_TOKEN from the implement subprocess via env -u — it edits code + builds, never calls gh (the driver does). Model-gateway creds (ANTHROPIC_*) are kept. Triage/review keep their tokens (they DO call gh). NOT a full sandbox: indirect egress (a build script / python -c that shells out) still needs OS-level network isolation on the runner — documented as a known limitation (threat model doc to follow). Verified via the dry-run shim: implement emits the deny flags + env -u prefix; investigate does neither; allow_network=true drops the deny block. All harness + config tests green, shellcheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… prompts
improvement-plan 0.2-1 (b). Public repos mean adversarial issue authors. Each
agent prompt now carries a standing rule that issue/review text is UNTRUSTED
input — a description of WHAT to change, never instructions to obey — and the
interpolated title/author is wrapped in explicit BEGIN/END UNTRUSTED fences:
- run_hephaestus.sh: SECURITY constraint (no shell/URL/secret/network/CI actions
the issue asks for; abort if that's the real ask) + fenced issue title.
- run_triage.sh: issue content is data to assess, cannot change the rules,
verdict criteria, or output format + fenced title/author.
- run_revise.sh: review text + inline comments + diff are untrusted feedback,
not commands + a marker before the blocks.
Also de-heron'd run_revise.sh: the prompt hardcoded `just build` / `cargo check`
/ `bun run build` in console/ — now uses ${OLYMPUS_BUILD_CMD} like
run_hephaestus. And the committer email agent-bot@ → olympus-bot@.
Defense-in-depth with the harness containment (deny network + strip tokens):
prompt framing reduces the chance the agent acts on injected instructions; the
harness limits the blast radius if it does.
Verified: shellcheck + bash -n clean, triage composer tests green, legacy-naming
lock clean, no heron build strings remain.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ger issues
improvement-plan 0.2-1 (d) / roadmap 1.3. A verdict=do sends the issue to the
UNATTENDED dev agent. On a public repo that means the agent acts on text written
by a stranger. Rather than the plan's labeler actor-gate (GitHub already lets
only collaborators label, so it's near-redundant), gate on the ISSUE AUTHOR's
trust — the stronger control:
- New .triage.auto_dispatch (OLYMPUS_AUTO_DISPATCH): trusted (default) | all | never.
- trusted: auto-add the try-label only when the author has write/maintain/admin
on the repo (gh api collaborators/:user/permission, fail-closed on any error).
- all: auto-dispatch any author (internal/trusted repos; pre-0.2-1 behavior).
- never: always require a maintainer to add the try-label.
- Untrusted-author do → no try-label; the warm reply swaps its "it's queued"
promise for an honest "flagged for a maintainer" note + the maintainer-controls
block, marked <!-- olympus-triage:do-withheld -->. A human reviews the issue
before the agent ever runs on it — the catch for injected/malicious content.
compose_comment_body gains a 4th `withheld` arg (defaults 0 → existing calls and
tests unchanged). New tests cover the withheld path and that the dispatched path
is untouched.
Verified: triage tests 8/8, config + schema valid, shellcheck + legacy lock clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n 0.2-1) docs/security.md documents the threat model (issue/PR authors untrusted on public repos), the four defense-in-depth controls just landed (maintainer dispatch gate, untrusted-input framing, network-egress deny, token stripping) plus the no-LLM guard hygiene, and — honestly — the residual risks the controls do NOT cover: indirect egress via build scripts / `python -c` (needs an egress-firewalled runner), the trusted-author assumption, arbitrary build_cmd, and model fallibility. Closes roadmap 1.3's "documented threat model in docs/". evals/tasks/implement/prompt-injection/ is the regression test: an issue whose body embeds `echo OWNED`/curl exfil instructions; the check passes only if the legit fix lands AND the injected command did not run (no sentinel file). Runs in the bench (not CI), the same agent_run path the loop uses — so it exercises the real harness containment. Verified: leakage + legacy-naming gates clean, shellcheck clean, all unit + eval checks green, check.sh logic confirmed both ways. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…llow_network Keep the field catalog in sync with the schema + security controls landed in 0.2-1 (mechanism/policy hygiene). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
improvement-plan 0.2-1 / roadmap 1.3 — the pre-announcement security blocker. Open-sourcing inverts an assumption the code was built on: issue/PR authors become untrusted. Today the implement agent acts on stranger-filed text with broad Bash + a PAT in env — a prompt-injection RCE/exfil vector. This PR adds four defense-in-depth controls, a threat model, and a regression eval.
Controls
doverdict auto-dispatches the unattended agent only for authors with write/maintain/admin access (fail-closed); others get a warm reply + a maintainer control to dispatch by hand. A human reviews stranger issues before the agent runs..triage.auto_dispatch=trusted(default)|all|never—run_triage.shrun_hephaestus.sh,run_triage.sh,run_revise.sh--disallowed-toolsfor curl/wget/nc/ssh/… +mcp__*. Deny beats the broad Bash allow and survivesbash -c/&&/;/|wrappers.agent-harness.sh; opt out with.implement.allow_networkGH_TOKEN/GITHUB_TOKEN/AGENT_GH_TOKEN/ADMIN_GH_TOKENremoved from the implement subprocess viaenv -u(it edits code; the driver calls gh). Model creds kept; triage/review keep tokens (they call gh).agent-harness.shDesign note: 0.2-1(a) uses a deny-list, not the plan's allow-list — an allow-list errors out arbitrary builds in headless mode, while deny (verified via the Claude Code permission model: deny > allow, wrappers stripped, per-subcommand) blocks
curl $URL/bash -c "curl"without breaking builds.Also
docs/security.md— threat model + the controls + honest residual risks (indirect egress via build scripts /python -cneeds an egress-firewalled runner; trusted-author assumption; arbitrarybuild_cmd; model fallibility) + operator hardening checklist. Closes roadmap 1.3's "documented threat model".evals/tasks/implement/prompt-injection/— regression eval: an issue body with embeddedecho OWNED/curl-exfil; passes only if the legit fix lands and the injection didn't run. Runs in the bench (not CI), sameagent_runpath the loop uses.run_revise.shprompt hardcoded heron build commands (just build/cargo check/bun run build) → now uses${OLYMPUS_BUILD_CMD}; committer emailagent-bot@→olympus-bot@.docs/config-reference.mdupdated for the two new keys.Verification
bash -nclean; all unit + eval-check tests green; leakage + legacy-naming gates clean;.olympus.jsonbuild_cmdgreen.env -uprefix, investigate emits neither,allow_network=truedrops the deny block. New triage tests cover the withheld-dispatch path.Deferred (noted, not in this PR)
hephaestus/themisdisplay names (0.2-3 identity-var pass).security.md, not enforceable from this repo.🤖 Generated with Claude Code