Skip to content

Spike: make policy.json tamper-evident + define a secure edit/maintenance path (not in the vault) #72

Description

@hellno

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_approval directly, 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.)

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecuritySecurity-relevant

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions