cadrumo.adapters.persistence.profile.recipient_replay_guard module¶
Encrypted persistence adapter for recipient-package replay-nonce ledgers.
Every RecipientEncryptedPackage carries a
fresh, unique envelope_nonce_hex minted at encryption time (see
review_package_recipient_encryption). This
module lets the recipient side of
decrypt_review_package_for_recipient()
record which nonces have already been successfully decrypted, so a captured
ciphertext replayed a second time against the same recipient bucket is
refused rather than silently re-accepted.
The nonce ledger is a bucket-scoped append-only consumption record, following
the exact governed-repository shape of the trusted-recipient registry: one
FINANCIAL-sensitivity secure-object singleton per bucket, an empty ledger
when absent, and mark_consumed refuses a nonce already on file. This is
the aeat-architecture-boundaries companion to that
registry – the decrypt primitive itself performs no persistence; a caller
(the future CLI decrypt verb) composes this ledger’s check_and_consume
around the existing, unmodified
decrypt_review_package_for_recipient() call.
The encrypted row’s storage policy is governed by
SensitivityClass.
mark_consumed composes
ProfileBareModelSecurePersistence’s
mutate for its read-mutate-write-with-retry mechanic, rather than
hand-rolling the loop: the ledger is stored bare (ConsumedNonceLedger.model_dump_json()
directly, no Envelope wrapper), matching that kernel’s wire shape exactly,
and the “refuse a duplicate nonce unretried, else append and retry only on a
genuine revision conflict” logic is precisely the mutate contract. load
stays hand-rolled because it translates a bare OSError into
RecipientReplayGuardError, a translation the kernel does not perform.
Nonce identity is clock-free (the nonce is a random 32-byte value minted once
per encryption, never derived from a timestamp), so replay defence does not
depend on wall-clock ordering the way the paired expiry check does – see
review_package_recipient_encryption for the
issued_at / valid_until expiry fields, which are a distinct concern
(a package can be replayed within its validity window, and expiry alone does
not detect a same-nonce replay before the deadline).
See also
review_package_recipient_encryptionMints the
envelope_nonce_hexthis ledger consumes and defines the paired expiry fields.review_package_recipient_registryThe structural template this repository mirrors.
- exception RecipientReplayGuardError(message=None, *, context=None, translated_message=None)[source]¶
Bases:
CadrumoErrorBase error for recipient-package replay-guard failures.
- Parameters:
- Return type:
None
- code¶
- exception RecipientPackageReplayedError(message=None, *, context=None, translated_message=None)[source]¶
Bases:
RecipientReplayGuardErrorRaised when a nonce already recorded as consumed is presented again.
A captured recipient-encrypted package replayed against the same bucket (whether by an adversary or by an operator’s own accidental re-run) is refused – the nonce is single-use once consumed.
- Parameters:
- Return type:
None
- code¶
- class ConsumedNonceRecord(**data)[source]¶
Bases:
BaseModelOne consumed
envelope_nonce_hexon file, with its consumption time.- Parameters:
data (Any)
- nonce_hex: str¶
- consumed_at: UtcInstant¶
- class ConsumedNonceLedger(**data)[source]¶
Bases:
BaseModelA bucket’s full set of consumed recipient-package replay nonces.
- Parameters:
data (Any)
- records: tuple[ConsumedNonceRecord, ...]¶
- class RecipientReplayGuardRepository(*, bucket_id=None, objects=None)[source]¶
Bases:
objectGoverned repository for the encrypted consumed-nonce ledger.
The singleton row is owned by
MODELO_REVIEW_PACKAGE_RECIPIENT_REPLAY_GUARD_NAMESPACEand persisted throughSecureObjectRepository, mirroringRecipientFingerprintRegistryPorts.- Parameters:
bucket_id (str | None)
objects (SecureObjectRepository | None)
- load()[source]¶
Load the ledger, returning an empty ledger when absent.
- Raises:
RecipientReplayGuardError – When the envelope exists but the filesystem I/O itself fails.
DecryptionError – When the envelope exists but its ciphertext fails AEAD authentication (tampered or corrupted at rest) – propagated verbatim rather than re-wrapped, so a caller can distinguish “this ledger was tampered with” from a generic I/O failure. This is the anti-tautology proof this repository’s roundtrip tests require: a corrupted on-disk payload must be refused loudly, not silently coerced into a plausible-looking empty ledger (which would re-open every previously-consumed nonce to replay).
- Return type:
- mark_consumed(nonce_hex, *, consumed_at=None)[source]¶
Atomically record
nonce_hexas consumed.- Parameters:
nonce_hex (
str) – The envelope’senvelope_nonce_hex(seeRecipientEncryptedPackage).consumed_at (
datetime|None) – Optional override for the record’sconsumed_attimestamp (tests only); defaults to the current UTC time.
- Raises:
RecipientPackageReplayedError – When
nonce_hexis already on file – the package has been presented for decryption before.- Return type: