Skip to content

fix(durable): wire control-entry HMAC to a production key with read-side verification#6130

Merged
bug-ops merged 1 commit into
mainfrom
fix/6043-durable-control-hmac-wiring
Jul 12, 2026
Merged

fix(durable): wire control-entry HMAC to a production key with read-side verification#6130
bug-ops merged 1 commit into
mainfrom
fix/6043-durable-control-hmac-wiring

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

LocalBackend::with_hmac_key (the row-level control-entry HMAC documented at INV-8 for shared-DB deployments) had no production caller, so the hmac field on EffectIntent control rows was always stamped None, and no code path ever recomputed or verified it on read — the spec's claim that this closes the EffectIntent-forgery attack vector (security HIGH-2b) was false in code.

  • Derive the control-entry HMAC key as a BLAKE3 derive_key subkey of the same vault-resolved ZEPH_DURABLE_KEY used for the AEAD payload cipher (distinct domain-separation context, no new vault secret).
  • Wire the key at the same runtime choke points PR fix(durable): enforce encryption_gate INV-8 policy at runtime #6040 used for the AEAD cipher: the shared P1/P2 durable bootstrap path, the scheduler daemon adapter, and the CLI read path (zeph durable list/show/inspect/prune/resume).
  • Gate key resolution on the existing shared_db/postgres:// detection policy — no new config surface, so no --init/--migrate-config changes were needed.
  • Add read-side verification in LocalBackend::row_to_entry: every EffectIntent row is recomputed and constant-time-compared against its stored HMAC, failing closed with a new DurableError::ControlIntegrity variant on mismatch.
  • Close a review-caught residual: an unkeyed reader that encounters a row still carrying a stamped HMAC now fails closed too, instead of silently trusting the row as unverified plaintext (reachable via shared_db config drift between a writer and a reader over the same physical file).

Single-user local SQLite is unaffected — hmac stays None and unverified, matching the documented INV-8 stance.

Closes #6043
Closes #6044

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — 13008 passed, 0 failed
  • cargo test --doc --workspace --features "desktop,ide,server,chat,pdf,scheduler" — all doc-tests pass
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • New unit tests: HMAC verified successfully under the correct key; rejected under a wrong key; rejected when missing on a keyed backend; rejected when present but the reader is unkeyed (the review-caught residual); key-derivation determinism and domain separation from the AEAD key; fail-closed/resolves-correctly key loading for single-user vs. declared-shared-DB configs

@github-actions github-actions Bot added documentation Improvements or additions to documentation rust Rust code changes bug Something isn't working core zeph-core crate size/XL Extra large PR (500+ lines) labels Jul 12, 2026
…ide verification

LocalBackend::with_hmac_key had no production caller, so the row-level
control-entry HMAC documented in INV-8 was never computed, and no
read-side verification existed at all. Derive the key as a BLAKE3
subkey of the vault-resolved ZEPH_DURABLE_KEY, wire it at the same
choke points as the AEAD payload cipher, gate it on the existing
shared_db policy, and verify every EffectIntent row on read. An
unkeyed reader that encounters a row still carrying a stamped HMAC
now fails closed instead of silently trusting it.
@bug-ops
bug-ops force-pushed the fix/6043-durable-control-hmac-wiring branch from 8fa4eab to 5047732 Compare July 12, 2026 11:53
@bug-ops
bug-ops merged commit 5b6e057 into main Jul 12, 2026
47 checks passed
@bug-ops
bug-ops deleted the fix/6043-durable-control-hmac-wiring branch July 12, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working core zeph-core crate documentation Improvements or additions to documentation rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

1 participant