You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Decision update (2026-06-20 — ADR 0004): decoupled from #71 — this needs no anchor and no shared mechanism. Build the local fix: a MAC keyed from vault material + fail-closed load + an inert monotonic version field. Anti-replay of an old valid policy is deferred with #71 (it needs the AEAD-AAD format break and only beats an attacker weaker than the same-uid one the threat model already concedes). Everything else below stands.
Why
policy.json is the load-bearing artifact for Deckard's headline claim — "an agent can't move funds beyond policy" — yet it's the least-protected file in the system: plaintext, unauthenticated, read at boot via serde_json::from_slice (crates/deckard-signerd/src/policy_store.rs:61). An attacker with filesystem write can edit caps, allow_to, allow_swap_tokens, or require_approvaldirectly, and the signer can't tell. The keys live in an authenticated vault; the rules guarding the keys live on a sticky note next to it. Close that asymmetry.
This is a different and larger gap than vault rollback (#71): rollback needs an old genuine copy, whereas the policy can just be rewritten in place. Anti-rollback alone does nothing for it.
Realistic attacker (severity context, THREAT-MODEL.md voice). This requires code-execution / file-write as the user — the key-less MCP sidecar has no SetPolicy and cannot do it via prompt injection (fixed tool set). So it bites the weaker attacker (a bad backup, a sync glitch, a sandboxed/limited app) and hardens a trusted-side artifact. It is "make the trusted-side rulebook tamper-evident," not "the agent can edit its own rules today." State this honestly in the issue so it doesn't overclaim. (Note: against a full same-uid attacker post-unlock the MAC key is derivable, so the MAC is tamper-evidence raising the floor, not a defense against the conceded boundary — same honesty as #71.)
Constraint / non-goal
Do NOT move policy into the encrypted vault. Keep it a first-class, separately maintainable, inspectable artifact. The goal is a clean, secure way to store, verify, and edit policy — not to bury it in vault.bin.
Spike questions to answer
Authentication. Make the file tamper-evident without encrypting it into the vault: a detached MAC / signature under a key the signer holds (derived from the keystore, or a signer-managed key). Verify-on-read; refuse or flag on a broken/missing tag.
Edit & maintenance path. Today there is no SetPolicy API and policy is hand-edited plaintext (policy_store.rs:7). Should all writes go through the daemon's authenticated control channel (the same capability-gated path that authorizes Resolve), so only the trusted signer ever (re)writes and re-seals the file? Define the human/agent edit UX that replaces hand-editing.
Human-readability vs tamper-evidence. Can it stay greppable/inspectable while authenticated — readable JSON + a sidecar tag, or canonical-JSON + MAC — and what happens when a user legitimately hand-edits (re-seal flow vs reject)?
Rollback (anti-replay) — DEFERRED. Do not stand up an anchor for this. Replaying an old valid policy needs file-write (same-uid) and only beats a weaker-than-conceded attacker; deferred with [Deferred] Spike: rollback-protect the vault with a monotonic epoch (→ ADR 0004) #71 (ADR 0004). Write the monotonic version field now (inert); enforce later only if blockers B1–B3 clear.
Fail-safe. Keep and extend the current loud fail-safe (policy_store.rs — tight default on missing/invalid): a tampered/unsealed policy fails safe to the tight default plus a loud warning, never silently obeyed.
Out of scope
Folding policy into the vault (explicit non-goal above).
New policy fields or semantics — this is about integrity + the edit path, not policy expressiveness.
Done when
A short design note or ADR choosing: the authentication mechanism, the secure edit/maintenance path (likely daemon-mediated), the human-readability tradeoff, and the fail-safe behavior — or a recorded decision to defer with concrete blockers. (Anti-replay is already deferred — see #71 / ADR 0004.)
Why
policy.jsonis the load-bearing artifact for Deckard's headline claim — "an agent can't move funds beyond policy" — yet it's the least-protected file in the system: plaintext, unauthenticated, read at boot viaserde_json::from_slice(crates/deckard-signerd/src/policy_store.rs:61). An attacker with filesystem write can edit caps,allow_to,allow_swap_tokens, orrequire_approvaldirectly, and the signer can't tell. The keys live in an authenticated vault; the rules guarding the keys live on a sticky note next to it. Close that asymmetry.This is a different and larger gap than vault rollback (#71): rollback needs an old genuine copy, whereas the policy can just be rewritten in place. Anti-rollback alone does nothing for it.
Realistic attacker (severity context,
THREAT-MODEL.mdvoice). This requires code-execution / file-write as the user — the key-less MCP sidecar has noSetPolicyand cannot do it via prompt injection (fixed tool set). So it bites the weaker attacker (a bad backup, a sync glitch, a sandboxed/limited app) and hardens a trusted-side artifact. It is "make the trusted-side rulebook tamper-evident," not "the agent can edit its own rules today." State this honestly in the issue so it doesn't overclaim. (Note: against a full same-uid attacker post-unlock the MAC key is derivable, so the MAC is tamper-evidence raising the floor, not a defense against the conceded boundary — same honesty as #71.)Constraint / non-goal
vault.bin.Spike questions to answer
SetPolicyAPI and policy is hand-edited plaintext (policy_store.rs:7). Should all writes go through the daemon's authenticated control channel (the same capability-gated path that authorizesResolve), so only the trusted signer ever (re)writes and re-seals the file? Define the human/agent edit UX that replaces hand-editing.policy_store.rs— tight default on missing/invalid): a tampered/unsealed policy fails safe to the tight default plus a loud warning, never silently obeyed.Out of scope
Done when
A short design note or ADR choosing: the authentication mechanism, the secure edit/maintenance path (likely daemon-mediated), the human-readability tradeoff, and the fail-safe behavior — or a recorded decision to defer with concrete blockers. (Anti-replay is already deferred — see #71 / ADR 0004.)
Files
crates/deckard-signerd/src/policy_store.rs,crates/deckard-signerd/src/daemon.rs(control channel + load path),crates/deckard-signerd/src/config.rs,crates/deckard-contract/src/policy.rs.Related: #71 (vault rollback — deferred & decoupled, ADR 0004). Grounding:
THREAT-MODEL.md.