0.13.2 — security: the oplog was writing plaintext on encrypted databases
0.13.2 — security: the oplog was writing plaintext on encrypted databases
If you use encryption_key / new_encrypted, upgrade. Security advisory: GHSA — plaintext record content retained in oplog.payload on encrypted databases.
The defect
On any database opened with a key, every record's complete payload — text, metadata, embedding — was written to oplog.payload as unencrypted JSON and retained there (applied rows are kept, because the oplog doubles as the replication stream). memories.text was properly AES-256-GCM sealed; entities, claims and FTS were correctly excluded. Only the write-ahead projection leaked.
is_encrypted returned true the entire time. An operator who did the responsible thing — enable encryption, verify the flag — received an affirmative answer while plaintext sat on disk. The verification surface confirmed a guarantee the storage did not provide. If you audited an affected database and saw true, that check was uninformative.
- Affected:
>= 0.7.0, < 0.13.2, encrypted databases only. - Not affected: databases opened without a key — their oplog rows are byte-identical before and after this release.
- Am I affected?
db.oplog_plaintext_rows()returns the number of unsealed rows. On an encrypted 0.13.2 database it must be0after open.
The fix
Oplog payloads are sealed with the database DEK and marked ENCv1:, so a row's state is classifiable without a key and a mixed-vintage oplog reads correctly. All seven write sites route through one encoder. Reads unseal at the two chokepoints every consumer flows through: the pending-ops drain (so no op type added later can forget) and extract_ops_since_enc for replication.
Existing encrypted databases heal on open — an idempotent, marker-guarded migration seals pre-0.13.2 rows. Rotate any secrets that were stored in an affected database and are cheap to rotate; the migration removes the plaintext from the live file, but it cannot reach backups taken before the upgrade.
The test that makes it permanent
encryption_canary.rs scans the raw file bytes — database and sidecars — and fails if any recorded content appears anywhere. It ships with its own control: an unencrypted database must show the canary, or the scan is measuring nothing. Every previous encryption test asked the engine whether it had encrypted something, which is the instrument answering for its own claim.
The rule this came from, credited to the partner gate that applied it to its own instrument the same day: a negative result must demonstrate its own sensitivity, in the same run, on the same build.
Verified
- Engine: 1,821 tests full, 1,739 slim, clippy
--all-targets --all-featuresclean on both crates. - Canary proof on a built wheel: seven canaries absent from the encrypted file, all seven present in the unencrypted control, recall and both key-refusal paths unchanged.
- Independently reproduced and cleared by the hermes-plugin gate on its own corpus with its own scanner.
- Retrieval path measured, not assumed: determinism clean across 28 queries × 8 bursts × 4 instances; gate v1.3.0 identical to 0.13.1 on every stable metric. The fix costs retrieval nothing.
Upgrade note
Wheels are now named yantrikdb-0.13.2-*. Scripted installs that pin a filename or glob a previous version will silently install a stale wheel — check yantrikdb.__version__ after installing.