fix: audit safety-override renew fail-closed before commit (#2453) - #2484
Conversation
Design Review (Fable 5) — ✅ PASSAdvisory design-level review of Design-Verdict: PASS Real audit gap, closed with the same fail-closed write-ahead pattern the file already uses for activation; the lock-release re-verify races are reasoned through and mutation-tested. [DESIGN-REVIEWED] 6995500 |
GPT 5.6 Review — ✅ human override acceptedHuman judgment by @bolichen97 overrides the GPT 5.6 finding for This comment is updated in place on each push. The model was not re-run because an authorized human decision supersedes it. False positive or not applicable? A repository writer can comment: |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsI've reviewed the full renew() logic, both Slack call sites, the SEL helper, and the AUTOSDE rules. Let me verify one concurrency concern about the refused-commit corrective SEL write not being a new blocking call on the loop, and confirm the happy path. The logic is sound: audit-before-commit with a lock-released window handled by re-verification (count snapshot + arm-held check), fail-closed on audit failure, and both Slack sites correctly offload the now-synchronous critical SEL write via No findings. [OPUS-REVIEWED] 6995500 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
45586bd to
7390c61
Compare
|
🤖 Kiro Crew Auto-Pipeline [operator: bolichen97] Both GPT 5.6 findings addressed in Blocker 2 (stale renewal overwrites newer activation) — fixed as suggested. Blocker 1 (sync SEL I/O on the event loop) — fixed by offloading, not by reverting. Reverting the critical audit would reintroduce the exact vulnerability this PR closes (#2453). Instead the two async call sites ( Backend lint failure is the base-branch |
7390c61 to
a15db9a
Compare
|
🤖 Kiro Crew Auto-Pipeline [operator: bolichen97] GPT round-2 blocker (false renewal audit on concurrent permanent activation) fixed in Also rebased onto main now that the base isort break is fixed (#2479). |
renew() extends the same auto-approval authority that activate() and activate_scoped() guard, but it committed the extended deadline first and wrote its SEL event afterwards with critical unset, so a failed audit write was swallowed and the extension stood unrecorded. Bring renew() onto the file's fail-closed audit-before-commit pattern: the renew SEL event is written with critical=True before the deadline moves, and an audit failure returns not-renewed (reason: audit_failed) with the grant untouched. Because the SEL write must not run under the state lock, eligibility is re-verified under the re-acquired lock before committing, so a grant deactivated during the audit window is not resurrected; a refused commit emits a corrective denied event so the SEL never silently over-states authority. Closes #2453
a15db9a to
6995500
Compare
|
🤖 Kiro Crew Auto-Pipeline [operator: bolichen97] GPT round-3 blocker (grace recheck can undo an explicit off) fixed in |
|
🤖 Kiro Crew Auto-Pipeline [operator: bolichen97] GPT round-4 finding is rebutted as a pre-existing-behavior false positive, with evidence:
Rounds 1-3 findings were all real and are fixed; this one asks this PR to fix #2475's bug in a way that regresses the lock discipline. /ai-review override gpt 6995500: Scenario is pre-existing main behavior rooted in deactivate()'s lapsed-grant early return (sibling issue #2475, explicitly out of scope here), and the proposed single-lock fix would put SEL filesystem I/O under _lock against the issue's stated lock discipline. |
|
/ai-review override gpt 6995500: Scenario is pre-existing main behavior rooted in deactivate()'s lapsed-grant early return (sibling issue #2475, out of scope per the issue's coordination note), and the proposed single-lock fix would put SEL filesystem I/O under _lock against the issue's stated lock discipline. |
Human judgment recorded@bolichen97 marked the gpt AI finding as false positive, not applicable, or explicitly accepted for
This decision applies only to this commit. A new push requires a new judgment. |
…v#2453) (kirodotdev#2484) renew() extends the same auto-approval authority that activate() and activate_scoped() guard, but it committed the extended deadline first and wrote its SEL event afterwards with critical unset, so a failed audit write was swallowed and the extension stood unrecorded. Bring renew() onto the file's fail-closed audit-before-commit pattern: the renew SEL event is written with critical=True before the deadline moves, and an audit failure returns not-renewed (reason: audit_failed) with the grant untouched. Because the SEL write must not run under the state lock, eligibility is re-verified under the re-acquired lock before committing, so a grant deactivated during the audit window is not resurrected; a refused commit emits a corrective denied event so the SEL never silently over-states authority. Closes kirodotdev#2453 Co-authored-by: Bolin Chen <>
Summary
SafetyOverride.renew()extended a YOLO grant's deadline first and wrote itssafety_override:renewSEL event afterwards, non-critically — a failed audit write was swallowed and auto-approval stayed extended with no record. Every other path in the file that creates auto-approval authority (_commit_activation,activate_scoped) audits fail-closed BEFORE committing.This brings
renew()onto the same pattern:_lock(no mutation): permanent-grant and denied early paths are byte-for-byte behavior-preserved.renewedSEL event is written withcritical=True; on failurerenew()logs at error level and returnsRenewResult(renewed=False, ttl=0, reason="audit_failed")with the deadline unmoved. (No call site or frontend matches reason strings — both Slack call sites branch only on.renewed.)renew()re-acquires_lockand re-derives the full live-or-in-grace predicate before committing. A concurrentdeactivate()(which zeroes_expires_at) during the audit window refuses the commit — no resurrection. A refused commit emits a correctivedenied/reason:not_active_at_commitevent so an auditor never reads a renewal that didn't take effect.current_adhoc_duration()resolution are unchanged.Tests (mutation-verified)
test_sel_crash_refuses_renew_and_leaves_deadline_unmoved— patches the SINK (log_api_accessraises, not_log_sel, which would pass vacuously regardless of thecriticalflag): asserts not-renewed AND deadline unmoved. Fails on unfixed code.test_renew_does_not_resurrect_grant_deactivated_during_audit— sink side effect callsdeactivate()during the audit window: asserts no resurrection. Fails on unfixed code.test_renew_extends_deadline_with_exactly_one_renewed_event— happy-path pin.Verification
token_auth.pyisort failure is a base-branch break, fix already open as style: restore import order in dashboard token_auth (unblocks all PRs) #2478 — will rebase once it lands).security-deep-dive.md,modules/security.md).Residual notes for reviewers
activate()pattern exactly (same sink, same flag); renewals are rare, human-triggered events.audit_failed, the Slack handlers' failure text still says "not active" (they branch only on.renewed). Fail-closed and out of this issue's scope; happy to file a follow-up for distinguished messaging.Coordination: scoped strictly to
renew()— does not touchdeactivate()(#2475) nor pre-emptrenew_lease()(#2443).Closes #2453