Mint per-segment grants for shell chain approvals - #598
Conversation
Chains of 5+ segments were accept-once only: no scope was offered and mintGrant refused to persist anything, so the same long chain re-prompted forever no matter what had already been approved. Below that threshold, multi-segment chains only minted a grant for the exact whole-string match, so a different chain sharing segments re-prompted too. mintGrant now decomposes the exact-chain scope into one Approval per real segment (reusing splitChainedCommand, the same splitter the gate's own per-segment evaluation loop already uses) instead of persisting a single whole-string pattern. Approving `a && b` grants `a` and `b` individually, so a later chain reusing either segment only prompts for what's new, and chains of any length behave the same way. This is strictly more permissive on later commands than a whole-string grant was, since a granted segment now reuses outside the chain it was first approved in. Nothing that previously auto-approved now prompts, and nothing that previously needed a fresh decision now silently skips one.
|
Holding this as draft pending an operator decision on the security posture change. Two things in this diff go beyond CL-5752's scope:
CL-5752 asked for chain segments to be evaluated against existing grants so an already-approved segment stops re-prompting. That is the read path and it was already in the loop. Minting N grants from one approval is the write path and is a separate, larger decision. Suggested split: keep the per-segment evaluation and the removal of the redundant |
Closes CL-5752.
Root cause
Two related gaps let a shell chain approval buy less than it should:
buildRequestsoffered no scope, andmintGrantrefused to persist anything even if a scope somehow arrived. The same long chain re-prompted every time regardless of what had already been approved.npm i && curl x) only ever minted a grant for the exact whole-string pattern. A different chain sharing the same segments (npm i && curl y) still re-prompted from scratch.The auto-approval evaluation loop in
gate.tsalready checks each segment independently viaevaluateApprovals— the violation was entirely on the minting side.Fix
MEGA_CHAIN_SEGMENT_THRESHOLD,MEGA_CHAIN_NOTICE, and their usages (isMegaChain, the mega-chain ask rule, the notice, the fast-pathhasExactFullCommandGrantshortcut it motivated).mintGrant'srun_shellpath now decomposes the exact-chain scope into oneApprovalper real segment, reusing the existing quote-awaresplitChainedCommandrather than adding a second parser.scripts/approval-forensics.ts.Behavior change — stated plainly
Granting per segment is strictly more permissive on later commands than granting one whole string was: a segment approved inside one chain now reuses on its own, or inside a different chain, later. Concretely:
a && bthen later runningb && cnow prompts only forc(previously it would re-ask for the whole new chain).Nothing that previously auto-approved now prompts, and nothing that previously required a fresh operator decision now silently skips one — an ungranted segment (including one hidden behind a wrapper like
bash -c "granted && ungranted") still asks.Tests
Updated the now-obsolete tests in
permission.test.tsandtests/unit/permission/cross-commit-composition.test.ts, and added the discriminating cases:a && bthen runningb && cprompts only forcbash -c "granted && ungranted") still promptsbun run checkpasses.Test plan
bun run checkgreen