close
Skip to content

SafetyOverride.renew() extends a grant before auditing it, and swallows audit failure #2453

Description

@chenmingwei23

What

SafetyOverride.renew() — the human-initiated renewal of a time-limited safety override (YOLO) grant — commits the extended deadline and then writes its SEL audit event non-critically. If the audit write fails, the failure is swallowed (_log_sel only re-raises when critical=True) and auto-approval stays extended with no record that it was.

Why

Every other path in this file that creates auto-approval authority already fails closed the other way round:

  • _commit_activation audits with critical=True before committing and returns an inactive result if the write fails. Its comment: "Audit BEFORE committing — fail-closed with no race window".
  • activate_scoped does the same: "Fail-closed audit before commit — no grant without a trace."

renew() extends the same authority those paths are careful about, so the asymmetry looks unintentional rather than reasoned — an auditor reconstructing "when was auto-approval live?" from the SEL log can be silently missing a renewal that happened.

This was surfaced while reviewing #2443, which added renew_lease() (the machine-issued counterpart). That method was flagged for exactly this weakness and now audits critically before committing, with the commit block re-verifying the guard because the audit runs with the lock released. renew() was deliberately left alone there to keep a focused change reviewable — hence this issue.

Additional Context

  • src/kiro_crew/safety_override.pyrenew(); compare _commit_activation and activate_scoped for the established pattern, and renew_lease() (post-fix(safety-override): lease the grant to armed loops instead of demoting mid-run #2443) for a worked example including the post-audit re-verify.
  • Note the lock discipline that matters here: the SEL write must not happen while holding _lock, so moving the audit before the commit opens a window in which a concurrent deactivate() could be undone. renew_lease() handles this by re-checking _expires_at <= 0 inside the commit lock; renew() would need the same.
  • Suggested test shape (mutation-verifiable): patch the SEL sink so log_api_access raises, call renew(), assert it reports not-renewed AND that the grant's deadline did not move. Injecting the failure at the SEL write rather than at _log_sel is what makes the critical=True flag load-bearing in the test — patching _log_sel itself raises regardless of the flag and passes vacuously.

Metadata

Metadata

Assignees

Labels

area: coreCore runtime utilities, config/paths, process singletonsauto-fixableTriage: mechanical fix, automated pipeline will handlebugSomething is not workingclaimedAn automated fix is in progressneeds-humanPR flagged for human review by drive-to-green pipelinesecurityHardening, trust boundary, or sandbox escape

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions