Skip to content

feat(durable): add HMAC and HWM rotation windows for zeph durable rotate-key - #6460

Merged
bug-ops merged 2 commits into
mainfrom
feat/issue-6451/durable-hmac-rotation-window
Jul 18, 2026
Merged

feat(durable): add HMAC and HWM rotation windows for zeph durable rotate-key#6460
bug-ops merged 2 commits into
mainfrom
feat/issue-6451/durable-hmac-rotation-window

Conversation

@bug-ops

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

Copy link
Copy Markdown
Owner

Summary

  • Adds LocalBackend::with_previous_hmac_key, mirroring the AEAD cipher's existing with_previous rotation window, so control-entry HMAC verification on a shared DB tries the current key then falls back to the previous key during a rotation window instead of force-aborting every in-flight execution.
  • Adds a drop-scan (count_control_entries_under_previous_hmac) covering the payload-less crash-orphan EffectIntent case the pre-existing AEAD blob-scan alone would miss; --drop-previous refuses if either scan is nonzero.
  • Removes --ack-shared-db-drain (flag, refusal gate, CLI surface) — it existed only because the HMAC key had no rotation window; that premise is now closed.
  • Adds a passive key_id / "rotation window open" indicator to the TUI durable panel, reusing the existing 5s poll task. No action button — rotation stays a restart-required CLI-only operation.
  • Fixes a related defect surfaced while rebasing onto the concurrently-merged transcript/durable tamper-evidence work (feat(security): add tamper-evidence for transcript, session, and durable journal history #6453): its high-water-mark (HWM) integrity key added a with_previous_hwm_key primitive but froze the rotation epoch at a constant, leaving the window unreachable, and never attached HWM keys on the scheduler-daemon read path at all. Any rotate-key followed by a restart force-aborted every pre-rotation execution's HWM check on all deployments (not just shared DBs — this is unconditional, unlike the control-HMAC gap). HWM rotation now rides the same key_id/previous_key_id lifecycle as the AEAD and control-HMAC windows, with a third drop-scan for executions whose checkpoint was folded after rotation (checkpoint_fold reseals under the new key without re-signing the HWM row, so neither existing scan would catch them).

Closes #6451
Closes #6450
Closes #6454

Known scope gap

#6450 also asked to map CipherError::UnknownKeyId "in the TUI reveal path" — no such path exists today (the TUI durable panel only has a passive metadata toggle, never touching the cipher). Not built speculatively; tracked in #6459 for if/when a TUI reveal path is added. The CLI's existing describe_reveal_error is already reusable for that future work.

Severity note on the HWM fix

Confirmed by a focused security audit: the HWM rotation gap this PR closes was a fail-closed availability defect (executions force-abort, never a silent integrity or confidentiality bypass — no deleted/tampered row ever went undetected because of it). No security advisory or out-of-band hotfix is warranted; shipping the fix here is sufficient. Tracked separately as #6454 for traceability since it's a distinct defect from #6451/#6450's original scope, discovered mid-implementation.

Deferred follow-up

open_durable_backend and both AgentBuilder::with_durable_* methods now take 5+ positional key-material Option parameters (AEAD current/previous, control-HMAC current/previous, HWM current/previous). Bundling these into a single DurableKeyMaterial struct was deliberately deferred — re-bundling mid-implementation would have re-touched every already-threaded call site for no correctness benefit (mis-wiring the current positional params fails closed). Tracked as #6458.

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 (14571 passed, 0 failed, 35 skipped)
  • Rustdoc gate (RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace ...)
  • gitleaks protect --staged
  • Regression test for the checkpoint_fold drop-scan gap (folds a pre-rotation execution post-rotation, proves the AEAD and control-HMAC scans both return 0 while the new HWM scan correctly refuses --drop-previous)
  • Regression test for scheduler-daemon HWM rotation-window resume, mirroring the existing control-HMAC scheduler-daemon test
  • CLI-read-channel try-both HMAC verification regression test
  • End-to-end --drop-previous HMAC-scan test with a payload-less previous-key-only EffectIntent
  • Error-classification test (benign rotation routes to key_epoch_unresolvable, never mis-reported as tamper)
  • .local/testing/playbooks/durable-key-rotation.md and .local/testing/coverage-status.md updated
  • Live agent session test — not run this session; recommend before merge per the LLM serialization gate's spirit given the crypto-sensitive surface, though this PR does not touch LLM request/response serialization directly

…ate-key

Control-entry HMACs on a shared DB were derived from ZEPH_DURABLE_KEY
with no rotation window, so rotating the key force-aborted every
in-flight execution; rotate-key only guarded this with an operator-trust
--ack-shared-db-drain flag. LocalBackend gains with_previous_hmac_key,
mirroring the AEAD cipher's existing with_previous window: verification
tries the current key then the previous key, and a new drop-scan
(count_control_entries_under_previous_hmac) covers the payload-less
crash-orphan EffectIntent case the AEAD blob-scan alone would miss.
--ack-shared-db-drain, its refusal gate, and its CLI surface are removed
now that a real window exists.

The TUI durable panel gains a passive key_id / rotation-window-open
indicator, reusing the existing 5s poll task.

Rebasing onto the concurrently-merged transcript/durable tamper-evidence
work (#6453) surfaced a related defect: its HWM integrity key added a
with_previous_hwm_key primitive but froze the rotation epoch at a
constant, leaving the window unreachable and the scheduler-daemon read
path entirely unattached, so any rotate-key followed by a restart
force-aborted every pre-rotation execution's high-water-mark check on
all deployments, not just shared DBs. HWM rotation now rides the same
key_id/previous_key_id lifecycle as the AEAD and control-HMAC windows,
with a third drop-scan for executions whose checkpoint was folded after
rotation (checkpoint_fold reseals under the new key without re-signing
the HWM row, so neither existing scan would catch them).

Closes #6451
Closes #6450
Closes #6454
@github-actions github-actions Bot added enhancement New feature or request documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate size/XL Extra large PR (500+ lines) and removed enhancement New feature or request labels Jul 18, 2026
New test-only open_execution calls in src/commands/scheduler_daemon.rs
(added for the HMAC/HWM rotation-window regression tests) tripped the
#6360 CI completeness gate, which requires every trusted-history reader
to appear in .github/integrity_audited.txt with a verdict.
@github-actions github-actions Bot added the ci CI/CD configuration label Jul 18, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 18, 2026 16:41
@github-actions github-actions Bot added the enhancement New feature or request label Jul 18, 2026
@bug-ops
bug-ops merged commit 99f6f0f into main Jul 18, 2026
47 checks passed
@bug-ops
bug-ops deleted the feat/issue-6451/durable-hmac-rotation-window branch July 18, 2026 16:50
bug-ops added a commit that referenced this pull request Jul 18, 2026
* docs(specs): sync specs/ with commits merged since v0.22.1

Covers the durable-execution key-rotation surface (#6447, #6451, #6460),
subagent live transcript forwarding (#6359/#6455), and web_search
follow-ups (#6457):

- specs/064-durable-execution: document the previously-unspecified
  `zeph durable rotate-key`/`--drop-previous` CLI, the unified AEAD/
  control-HMAC/HWM key_id/previous_key_id rotation lifecycle, the three
  drop-previous safety scans, and the durable_execution_integrity HWM
  table/folded_count column.
- specs/081-transcript-integrity: add addendum documenting the HWM
  key-rotation window fix (issue #6460) and correct stale README/MOC
  summaries that still described the whole-file downgrade gap as open
  after #6461 closed it.
- specs/044-subagent-lifecycle: add new section documenting opt-in live
  transcript forwarding (config, mechanism, invariants); cross-reference
  from specs/026-tui-subagent-management and specs/047-cli-modes.
- specs/006-tools/006-1-web-search: document resolved-address-set client
  caching and real backend-status propagation into egress telemetry.
- specs/README.md, specs/MOC-specs.md: refresh index summaries for all
  of the above.

* release: prepare v0.22.2

Bump version from 0.22.1 to 0.22.2, finalize the changelog entry, and
sync generated documentation (mdBook pages, crate READMEs, root
README) with the features and fixes merged since v0.22.1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD configuration core zeph-core crate documentation Improvements or additions to documentation enhancement New feature or request rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

1 participant