fix(security): server-authoritative confirmations + undo (fixes #12)#25
Conversation
The streaming agent handler trusted client-supplied `confirmed_tools` and `undo_stack` from the request body. Because the old `tool_id` was a plain `hash()` of the tool input, a client could pre-confirm a destructive tool it was never prompted for (bypassing the confirmation gate), and a spoofed `undo_stack` could steer an "undo" into deleting an arbitrary record. Make both authoritative server-side: - New `shared/session_state.py`: DynamoDB-backed per-session store keyed by an opaque session id derived from the *verified* (user_id, nation_slug) JWT claims (never client input). Confirmations are recorded when the server emits `confirmation_required`; a client-supplied `confirmed_tools` value is only honoured if the server itself recorded that exact tool_id for the session. Forged ids are dropped and re-prompted. Confirmations are single-use (consumed on execute). Confirmation checks fail CLOSED on store errors. - `tool_id` is now a deterministic SHA-256 (`compute_tool_id`) instead of the per-process-salted builtin `hash()`, so the prompt and confirm requests agree on the id across Lambda invocations (the old scheme silently broke the round-trip across cold starts). - Undo history is maintained server-side and returned to the client in the `done` event; the client's `undo_stack` body field is ignored. Undo helpers fail OPEN (no history rather than blocking); any resulting destructive tool still hits the confirmation gate. - Infra: new `SessionStateTable` (TTL on `expires_at`), IAM access, and `SESSION_STATE_TABLE` env var on the streaming function. Tests cover the forged-bypass attempt, the legitimate confirm→execute flow, session scoping, single-use consumption, fail-closed/fail-open behaviour, and the server-side undo store. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
✅ Ready for merge — confirmation state & undo history are now server-authoritative; the client can no longer forge a confirmation to execute a destructive toolVerified:
What changed (mechanism): A Residual risk: Low.
Note for Ian: the — autonomous pr-driver |
…r-side-confirmations # Conflicts: # infrastructure/template.yaml
What & why
The streaming agent handler trusted client-supplied
confirmed_toolsandundo_stackfrom the request body. Because the oldtool_idwas a plainhash()of the tool input, a malicious client could pre-confirm a destructive tool it was never prompted for (bypassing the confirmation gate), and a spoofedundo_stackcould steer an "undo" into deleting an arbitrary record.This makes confirmation state and undo history authoritative server-side.
Fixes #12.
How
src/lambdas/shared/session_state.py— DynamoDB-backed per-session store keyed by an opaquesession_idderived from the verified(user_id, nation_slug)JWT claims (never client input). A confirmation is recorded when the server emitsconfirmation_required; a client-suppliedconfirmed_toolsvalue is honoured only if the server itself recorded that exacttool_idfor the session. Forged ids are dropped and re-prompted. Confirmations are single-use (consumed on execute).tool_id—compute_tool_idnow uses SHA-256 over canonicalised input instead of the per-process-salted builtinhash(), so the prompt request and the confirm request agree on the id across Lambda invocations. (The old scheme silently broke the round-trip across cold starts.)doneevent; the client'sundo_stackbody field is ignored. Any destructive action an undo triggers still hits the confirmation gate.SessionStateTable(TTL onexpires_at), IAM access, andSESSION_STATE_TABLEenv var on the streaming function.Acceptance criteria
confirmed_toolspayload does not execute a destructive tool without a real prior confirmation round-tripVerification
pytest tests/ -q→ 572 passed (Python 3.11)mypy src/ --explicit-package-bases→ clean (strict)cfn-lint infrastructure/template.yaml→ cleanmoto(real DynamoDB sim): combinedADD … SET …accepted, string-set delete auto-removes the attribute (replay returns empty),expires_atstored as Number for TTL.🤖 Generated with Claude Code