Skip to content

feat(durable): payload AEAD cipher, AAD binding, max-payload fail-closed (#4945)#4967

Merged
bug-ops merged 1 commit into
mainfrom
feat/m28/4945-durable-payload-cipher-aead
Jun 6, 2026
Merged

feat(durable): payload AEAD cipher, AAD binding, max-payload fail-closed (#4945)#4967
bug-ops merged 1 commit into
mainfrom
feat/m28/4945-durable-payload-cipher-aead

Conversation

@bug-ops

@bug-ops bug-ops commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Implements #4945 (spec-064 C2) — the confidentiality and integrity boundary for journaled payloads in the durable execution layer. Part of epic #4707.

What

zeph-durable — crypto-free contract (INV-1):

  • PayloadCipher seal/open trait + PayloadAad location binding (execution_id/step_id/entry_kind/idem_key) with a deterministic, injective, versioned canonical_bytes encoding fed to the AEAD associated-data channel.
  • EntryKindTag discriminator; EntryKind::tag_enum/tag now delegate to it (single source of truth for the entry_kind column strings).
  • CipherError (metadata-only, INV-5) + a fail-closed From<CipherError> for DurableError (AuthenticationReplayIntegrity, Malformed/UnknownKeyIdDecode).
  • ensure_payload_within_limit read-side max_payload guard (INV-11): O(1), fail-closed before any decode, never panics.
  • DurableConfig::encryption_gate enforces INV-8: AEAD may be disabled only for a single-user local backend (DisabledLocalWarn); shared-DB / Restate reject it with DurableError::EncryptionRequired.

zeph-core::durable — concrete implementation (lives outside zeph-durable to keep it crypto-dependency-free, INV-1):

  • XChaCha20Poly1305Cipher: fresh 192-bit CSPRNG nonce per seal (INV-7), key_id || nonce(24) || ciphertext || tag(16) blob layout, a one-key rotation window (current + optional previous), and zeroized transient key material. Constructed from the vault ZEPH_DURABLE_KEY.

Acceptance criteria

  • FR-DE-15 / INV-7 — fresh 24-byte nonce per seal; nonce || ct || tag layout; round-trip test.
  • NFR-DE-06 — 10^6 seals produce 10^6 distinct nonces.
  • AAD binding — cross-step / cross-execution open() fails with ReplayIntegrity (fail-closed); ciphertext tamper fails authentication.
  • INV-11 / NFR-DE-05 — over-limit payload → PayloadTooLarge in O(1), no decode, no panic.
  • INV-6 — op_fingerprint/payload/AAD carry no resolved secret material (API doc note).
  • INV-8 — shared-DB / Restate reject encrypt_payload = false; single-user SQLite warns.
  • chacha20poly1305 adds no cargo deny advisory (reuses the existing transitive 0.10.1); clippy -p zeph-durable clean.

Out of scope (later issues)

Wiring the cipher into actual journal writes (C3/C4), ZEPH_DURABLE_KEY vault resolution + startup INV-8 gate emission at the binary (C6/#4949), HMAC for control entries (C3).

Tests

  • zeph-durable: 33 unit + 19 doc-tests.
  • zeph-core::durable: 11 unit + 2 doc-tests (incl. the 10^6-nonce uniqueness test, ~11s, under the 30s slow-timeout with no terminate-after).

Verification

  • cargo +nightly fmt --check — clean
  • cargo clippy -p zeph-durable -p zeph-core --all-targets -- -D warnings — clean
  • RUSTFLAGS="-D warnings" cargo check --workspace --all-targets --features desktop,ide,server,chat,pdf,scheduler --locked — clean
  • RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps -p zeph-durable -p zeph-core — clean (the only remaining warning, BuildError::MissingProviders, pre-exists on main)
  • cargo deny check advisories — only the pre-existing RUSTSEC-2025-0134 (rustls-pemfile, tracked in dep: rustls-pemfile 2.2.0 unmaintained (RUSTSEC-2025-0134) — migrate to rustls-pki-types #3772); chacha20poly1305 introduces none.

Docs

  • New security reference page Durable Journal Encryption (book/src/reference/security/durable-encryption.md) documenting the cipher, the ZEPH_DURABLE_KEY vault key, the INV-8 requirement, and the key-rotation policy.
  • CHANGELOG.md [Unreleased] updated.

Closes #4945

@github-actions github-actions Bot added documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate dependencies Dependency updates enhancement New feature or request size/XL Extra large PR (500+ lines) labels Jun 6, 2026
Add the journal payload confidentiality/integrity boundary (spec-064 C2, #4945).

zeph-durable (crypto-free contract, INV-1):
- PayloadCipher seal/open trait + PayloadAad location binding
  (execution_id/step_id/entry_kind/idem_key) with a deterministic, injective,
  versioned canonical_bytes encoding
- EntryKindTag discriminator; EntryKind::tag/tag_enum delegate to it (single
  source of truth for the entry_kind column strings)
- CipherError (metadata-only, INV-5) plus a fail-closed From<CipherError> for
  DurableError (Authentication -> ReplayIntegrity, Malformed/UnknownKeyId -> Decode)
- ensure_payload_within_limit read-side max_payload guard (INV-11): O(1),
  fail-closed before any decode, never panics
- DurableConfig::encryption_gate enforces INV-8: AEAD may be disabled only for a
  single-user local backend (DisabledLocalWarn); shared-DB/Restate reject it

zeph-core::durable (concrete implementation, keeps zeph-durable crypto-free):
- XChaCha20Poly1305Cipher: fresh 192-bit CSPRNG nonce per seal (INV-7),
  key_id || nonce(24) || ciphertext || tag(16) layout, one-key rotation window,
  zeroized transient key material; from_vault_bytes validates key length

Keyed from the vault ZEPH_DURABLE_KEY; key-rotation policy documented in the new
Durable Journal Encryption security reference page.

Closes #4945
@bug-ops bug-ops force-pushed the feat/m28/4945-durable-payload-cipher-aead branch from 32be7f7 to 7e69751 Compare June 6, 2026 21:22
@bug-ops bug-ops enabled auto-merge (squash) June 6, 2026 21:22
@bug-ops bug-ops merged commit 568de7f into main Jun 6, 2026
36 checks passed
@bug-ops bug-ops deleted the feat/m28/4945-durable-payload-cipher-aead branch June 6, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core zeph-core crate dependencies Dependency updates 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

Development

Successfully merging this pull request may close these issues.

feat(durable): payload AEAD cipher, AAD binding, max-payload fail-closed

1 participant