Screen only external tool results in Auto posture - #970
Merged
Conversation
Auto mode quarantined 236 tool results in prod over four weeks; two thirds were qm's own skill docs, repo files, and control-plane tool echoes, and none were a third-party injection. The classifier had no provenance signal, so it judged content shape, and instruction-shaped text is a coding agent's daily diet. Every tool result now carries a provenance class. Internal tools (goals, cron, background bookkeeping, finish_silently, write, publish) and workspace reads skip the classifier. Only external content is screened: surface reads, MCP, credential_exec, shared-handle reads, reached-room execs, and execute or background output whose command fetched from the network. The two screening hooks (screenToolResult and screenExternalContent) collapse into one that returns an outcome with the verdict reason, which now lands in the audit row and the quarantined session entry. Codex, Claude, and OpenCode harnesses gain the same hook, so tool results are screened consistently across harnesses. The rubric now frames injection as an authority problem and gives the classifier benign examples of documentation, code, and skill files. Background polls read the launching command from the durable process registry instead of a per-instance map. Replaying 672 live screens (222 quarantined, 450 allowed): 10 quarantines remain, all deliberate injection tests or credential material, and no previously allowed screen is newly flagged. Over the most recent 472 screens the flag count drops from 22 to 0. Model calls drop by roughly 70 percent.
16francej
force-pushed
the
security-screen-provenance
branch
from
September 8, 2026 02:14
c787013 to
295fb7f
Compare
Screen oversize external output in bounded chunks instead of failing open past the payload cap, so an injection buried deep in a long Slack thread or MCP response is still classified. Treat cron list/get results as external when they can carry other members' crons. Treat a background job whose stored command hit the redaction length cap as external, since the network shape of the full command is unknown. Drop the rubric example that declared a calendar-connect instruction safe, which would have misled the classifier on a fetched skill file.
The network-command regex guessed whether a shell command reached the network from its text and failed open for anything it did not list. Replace the guess with ground truth from the egress proxy. Every execute and background start now runs under its own egress credential: the tool context re-mints the turn's egress token with an execId claim and overrides the sandbox's proxy environment for that command only. The authz service stamps the core synchronously on an execution's first allowed connection (once per execId, over a signed POST to /v1/egress-stamp, or directly into the store when no core relay is configured) and denies the connection if the stamp cannot be recorded. After the command finishes, the tool context reads the stamp and reports egressed on the result. Background jobs persist their egress id in the process registry so later polls can read the same stamp. Provenance is then a lookup: no stamp means workspace, a stamp means external, no accounting at all fails closed to external. The regex is gone. Tests drive the same path through a fake proxy that stamps when a command mentions a URL.
An egress stamp that never arrives must not read as "this command stayed local". Three ways that could happen are now closed: the authz process without a shared stamp store denies per-execution connections instead of stamping into its own memory; the tool context only mints per-command credentials when the sandbox enforces egress at the network layer, so env-only proxy backends stay fully screened; and the store's table name is one shared constant. Chunked screening now overlaps adjacent windows and splits any window whose serialized form still exceeds the bound, so an instruction can neither straddle a boundary nor hide in an excised middle. Chunks classify four at a time and stop at the first strict verdict. cron runs, history, and memory results are external content: they can carry other members' text. The quarantine stub names its source so the model knows which read was flagged. The release key and tool label live in one helper shared by the tool layer and the orchestrator.
Drop the per-command egress credential, the proxy stamp, the stamp store and route, the registry column, and the canary plan. All of it existed to decide whether one shell command reached the network, and every version of that decision depended on a separately deployed proxy being upgraded and reachable, with silence misread as safety when it was not. Provenance is now purely by tool. execute and background output is always external and screened, as on main. The read tool is workspace. Internal bookkeeping tools are never screened. This keeps every security property of main for shell output and still removes the false positives from internal tools and workspace reads. The remaining noise is local shell reads, which a prompt nudge toward the read tool can chip at later.
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.
Auto mode was quarantining tool results that never left the agent's own workspace: skill docs read through the
readtool, repo files, and control-plane tool echoes. Across a month of live screenings, roughly two thirds of quarantines were false positives of this kind and none were a third-party injection. The classifier had no provenance signal, so it judged content shape.Change
finish_silently,write,publish,share) skip the classifier. Thereadtool is workspace and skips it too, except shared handles from another scope. Everything that can carry outside content is external and screened: surface reads, MCP,credential_exec, shared-handle reads, reached-room execs, shared crons,memory,history, and allexecuteandbackgroundoutput.Replay against live data
672 recent screens (222 quarantined, 450 allowed) replayed through the new pipeline.
Of the 29 that remain: 13 are deliberate injection test fixtures, 8 are the agent's own credential material (keychain responses, materialized env files), 6 are first-party skill docs read through the shell rather than the
readtool, and 2 are session-dump inspections. Classifier calls drop by about 22%.The remaining noise is local shell reads of first-party files. A prompt nudge toward the
readtool for files is the follow-up that shrinks it without weakening the screen.Known trade-off
Content fetched to disk and read back later through
readis not re-screened. Binary shared files and binary inbound attachments share this gap; the attachment one predates this change.