source
ADR-020: Persisted session re-verification on load (proposed)
Date: 2026-04-21
Date: 2026-04-21
Status
Proposed. Addresses finding #4 from the 2026-04-21 review.
Context
GovernanceSession.from_dict reconstructs a session from its
persisted JSON by reading passport_token and passport_claims
verbatim. The claims dict is trusted as-is; no re-verification
against the current lineage index, revocation list, or tightened
verifier logic happens on load.
Consequence: a session persisted BEFORE a tightening lands (e.g., before the orphan-child fail-closed rules in PR #10) stays live afterward via a cold proxy restart, bypassing the new invariants. An attacker who obtained a persisted session file at a permissive version keeps that authority after the fix lands.
The proxy’s revocation flow partially mitigates this — every
evaluate_tool_call consults the revocation list — but does NOT
re-walk lineage, does NOT re-verify signatures, does NOT re-apply
the current verifier’s chain-integrity checks.
Decision
Introduce a proxy-level session loader
GovernanceProxy._load_verified_session(path) that:
- Reads the persisted JSON.
- Calls
GovernanceSession.from_dict(data)to unmarshal. - Re-runs
verify_passport_token(session.passport_token)against the current public key and lineage index. - If re-verification fails, the session is ejected: disk file
deleted, in-memory registry skipped,
_logwrites a “stale_session_evicted” entry with the jti and reason. - If it succeeds, the reconstructed
claimsREPLACESsession.passport_claimsso any claim-drift (e.g., derived fields that verify_passport adds) is picked up.
All existing callers of GovernanceSession.from_dict go through
the new loader. Direct from_dict stays available for tests that
deliberately want to bypass re-verification.
Consequences
- Tightening the verifier now retroactively invalidates sessions that would fail under the new rules — the intended property.
- Cold proxy restart cost grows by one signature verify per session on disk. Benchmarks: typically <10ms per session; mitigable via a per-session sentinel (skip re-verify when the persisted sentinel matches the current public-key fingerprint).
- Tests must add two cases: (a) a session persisted with the old orphan-child permissive shape is evicted on load under the new rules; (b) a session persisted AT the current version loads unchanged.
Out of scope
- Migrating pre-ADR-016 session files to include lineage hints —
covered by the migration path in proxy.py’s
_reconstruct_lineage_from_sessions. - Attested receipts on disk — ADR-016 already requires receipt re-verification on chain walk; this ADR is about the live session object, not the evidence ledger.