Skip to content

ADR-012: Confirmation authority

  • Status: Accepted
  • Date: 2026-08-08
  • Accepted: 2026-08-08 — implemented as confirmation_providers.py (Phase 2); status field corrected to match already-merged implementation. The separate signing-side tool remains a genuinely unbuilt, separate deliverable (this ADR's own Non-goals) and a hard activation gate on its own, not reopened by this status correction.

Context

ConfirmationVerifier (src/pfsense_mcp/tier1/confirmation.py) is an already-implemented, already-tested Protocol with no concrete production implementation — store.confirm() fails closed when none is configured. A production mutation cannot be confirmed until a real verifier exists.

Options considered

Option Strengths Costs
Detached signature, owner key, local verification (recommended) Strong, offline, no new infrastructure beyond a signing workflow Requires a separate signing-side tool/workflow (out of pfsense_mcp's scope, tracked as its own deliverable)
Hardware-backed signature (security key, HSM) Strongest key custody Higher operator friction; good v2 upgrade, not required for v1 given the verifier interface is unchanged either way
Local confirmation file, atomically consumed Simple Weaker than a cryptographic signature unless the file itself carries one — reduces to "detached signature" with extra steps if done correctly, or is insecure if not
CLI challenge/response Familiar UX Only as strong as its underlying cryptographic derivation — reduces to the same recommendation if done correctly
MCP-carried confirmation evidence Convenient — no out-of-band step Only acceptable if the evidence itself carries an externally authenticated signature; a plaintext token or boolean over MCP is explicitly rejected (this is the exact class of attack — prompt-asserted approval — the entire confirmation-boundary design exists to prevent)

Recommendation

Detached Ed25519 signature over a canonical digest of every ConfirmationEvidence field except proof (implemented as confirmation_providers.signing_payload() — deliberately not ConfirmationEvidence.evidence_digest, which is circular as a signature pre-image; see the linked spec's Invariant I1 implementation note), verified locally against a pinned public key set with explicit authority_id-based rotation. Private key custody lives entirely outside the MCP server's host/process. Full specification: confirmation_authority.md.

Self-challenge

"Why Ed25519 specifically, not RSA or ECDSA which are more commonly supported across tooling?" — Ed25519 has no parameter-choice footguns (no curve selection, no nonce-reuse catastrophic failure mode like ECDSA's k-reuse issue), small keys and signatures, and fast verification — a better fit for a from-scratch, first-implementation signing scheme where minimizing the ways to misconfigure the cryptography matters more than maximizing tooling compatibility. cryptography, already a dependency per ADR-009, has first-class Ed25519 support, so this adds no additional library.

"Should a persistent nonce-replay database be mandatory before this ships, given the original red-team report listed 'nonce replay store' as an open item?" — Reconsidered explicitly, and the recommendation is no, not for v1: replay is already prevented structurally by contract_id/ operation_id exact binding (verified in ConfirmationEvidence.verify_bindings) and single-use-per-contract confirmation (RecoveryContract.with_confirmation refuses if already confirmed). A persistent nonce store would add real operational complexity (retention, restart durability, cleanup) against a threat this design already closes by other means. If a future review identifies a concrete replay path this reasoning misses, revisit — but "the original report listed it as an open question" is not itself evidence a database is required, and this ADR records the closed reasoning explicitly rather than leaving it as unresolved caution.

Consequences

Positive

  • No plaintext/prompt-based approval path exists anywhere in the system.
  • Key rotation representable via authority_id without a code change.
  • No new dependency beyond what encryption already requires.

Negative

  • Requires building and operating a separate signing tool/workflow, which is real, non-trivial operational tooling this ADR does not itself deliver.
  • An owner who loses access to the signing key/device cannot confirm any mutation until key recovery/rotation is complete — an intentional tradeoff (availability sacrificed for the confirmation guarantee), not an oversight.

Future migration path

Hardware-backed signing (security key, HSM) can replace the signing-side tool without any change to Ed25519ConfirmationVerifier — the verifier only ever sees a public key and a signature. Revisit the "no nonce-replay database" decision if a future adversarial review identifies a concrete replay scenario not already closed by contract-ID binding.

References