fix(security): key the spawn auto-approve rung on event identity (#6506) - #6929
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of The event attributes ( Design-Verdict: PASS Root-cause fix: the rung now keys on non-forgeable canonical identity at one shared seam, fail-closed, with the residual deny-parity gap explicitly filed (#6938). Suggestions
[DESIGN-REVIEWED] c98c6fa |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe change is a security hardening that keys the spawn auto-approve rung on canonical event identity ( No findings. [OPUS-REVIEWED] c98c6fa Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of First-Principles-Verdict: CONCERNS The fix earns its place at cause level, but slack keeps a private copy of the predicate the description says was deleted into the shared builder. What this change shipsIntent: stop a model-forged
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] c98c6fa |
82c5a9e to
8c9f835
Compare
8c9f835 to
4aaf0d3
Compare
The auto_approve_subagent_spawn rung consulted only the model-authored event title on every channel surface, so a SHELL command whose title was forged to "spawn_run" was auto-approved when the hook was enabled -- bypassing the hook gate's command inspection and the name-grant verification (#6504). The rung now keys on canonical, non-model-authored identity via one shared predicate, hooks.event_is_spawn_run — deny-by-default, no title fallback: event.tool_name (from _meta.kiro) must be "spawn_run", carry the mcp_identity_trusted provenance flag, and be served by the crew's own MCP server (session_directive.CORE_MCP_SERVER). The title must ALSO read "spawn_run" — not as identity, but because the channel PreToolUse gate keys deny rules on the title, so a rephrased-title spawn falls to the ladder instead of approving past a title-keyed deny. This exactly preserves the rung's pre-fix approval surface minus the forgeries. A foreign MCP server or built-in that merely names a tool spawn_run cannot ride the rung, and an event without canonical identity (no _meta.kiro, or the correlated provenance-cache miss) falls to the channel's normal approval ladder — a downgrade, never a hard block. Genuine spawn_run MCP calls stay auto-approved, so unattended fan-out remains unblocked. - messaging/driver.py: AutoApprovePredicate now takes the PERMISSION EVENT; the honour point passes the event, never the title. - messaging/dispatch.py build_auto_approve: predicate takes the event and delegates to event_is_spawn_run. - discord/ + telegram/ transport_dispatch: the inline title-only replicas are deleted in favour of the shared build_auto_approve. - slack/handler.py _should_auto_approve_spawn (native rung + transport lambda): takes the event, delegates to event_is_spawn_run. - Tests pin BOTH directions per surface (genuine spawn approved, forged shell title falls to the ladder) plus a structural pin that no surface re-inlines a title-only check. Mutation-verified: reverting the predicate to title-only or the driver call site to title-passing fails the new tests. Closes #6506
4aaf0d3 to
c98c6fa
Compare
bolichen97
left a comment
There was a problem hiding this comment.
Readiness passed on the current head, all checks green, mergeable against main, and no outstanding change requests from any reviewer. Approving as part of a maintainer sweep of fix-type PRs.
Summary
Fixes #6506.
The
auto_approve_subagent_spawnrung keyed solely on the model-authored event title, so any tool whose title readspawn_run— including a SHELL command the model re-titled — rode the rung and was auto-approved without a human prompt.The rung now keys on canonical, non-model-authored identity through one shared predicate,
hooks.event_is_spawn_run, used by every surface (messaging driver/dispatch, slack native + transport, discord, telegram). All four conjuncts must hold:event.tool_name == "spawn_run"— the canonical name from_meta.kiro.toolName, cached per toolCallId, never model-authored;event.mcp_identity_trusted— the provenance flag set only when both canonical-identity cache reads hit, so a future inline population path fails closed;event.mcp_server_name == CORE_MCP_SERVER(session_directive.CORE_MCP_SERVER) — a foreign MCP server or a built-in that merely names a toolspawn_runcannot ride the rung;event.title == "spawn_run"— not as identity (the title is forgeable and never sufficient), but because the channel PreToolUse gate keys deny rules on the title: a rephrased-title genuine spawn falls to the normal approval ladder instead of approving past a title-keyed deny. This keeps the rung's approval surface exactly its pre-fix shape, minus the forgeries.There is deliberately no title fallback: an event without canonical identity (backend that doesn't emit
_meta.kiro, or the correlated provenance-cache miss) simply does not ride the rung and falls to the channel's normal approval ladder (session trust / YOLO / interactive) — a downgrade, never a hard block. Genuinespawn_runMCP calls stay auto-approved, so unattended fan-out remains unblocked.The three inline title-only replicas (discord, telegram, and the lambda previously duplicated in slack transport) are deleted in favor of the shared
build_auto_approve/event_is_spawn_run, and a structural test pins that no module re-inlines atitle == "spawn_run"predicate.Review adoptions
CORE_MCP_SERVERpin; stale predicate signature indocs/system-specs/modules/messaging.md; over-broad structural assertion narrowed.session_directive.CORE_MCP_SERVERinstead of a duplicated constant.hooks.py/test_hooks.pyand their baseline prune are reverted (the gate only demandedtest_slack_handler_coverage.py, which stays formatted); deleted a stale title-floor clause frombuild_auto_approve's docstring.Testing
test/test_hooks.py—TestEventIsSpawnRun(all four conjuncts individually load-bearing, fail-closed floors, the GPT-lanesend_file-forgery and rephrased-title cases) +TestShouldAutoApproveSpawnon event doubles.test/test_messaging_driver.py::TestAutoApproveTool— driver passes the EVENT (not the title) to the predicate; forged shell titledspawn_runprompts; genuine spawn auto-approves.test/test_name_grant_surfaces.py::TestSpawnRungEventIdentity— per-surface pins (slack/discord/telegram/messaging) + structural pin that no module re-inlines a title-only predicate.🤖 Kiro Crew Auto-Pipeline [operator: CrysisDeu#0c98c3a2]