Most of the releases on this blog add a capability. This one removes a class of
quiet lie. v0.4.4 is a dedicated security hardening pass, started from the
v0.4.3 Python package baseline and folding in a Codex review that
read the code adversarially rather than charitably. The theme across all fourteen
fixes is the same: when Oversight cannot prove the safe thing, it must
fail closed — refuse, raise, reject — instead of silently
doing the convenient thing and reporting success.
Why the version moves off v0.4.3
The honest reason the package metadata jumps from 0.4.3 to
0.4.4 is so nobody confuses the hardened tree with the baseline that
preceded it. The historical v0.5.0 Rekor/Rust exploration still lives
in git history and the Rust workspace, but the download line that users install
from is this one. If you are on 0.4.3, the version string is the
signal: upgrade. A security release that is hard to distinguish from the thing it
fixes is a security release that does not get installed.
The container counts opens honestly now
A sealed container enforces a max_opens limit. The bug: the counter
incremented on every attempt, including attempts that never produced
plaintext. A recipient who failed to decrypt — wrong key, tampered bytes
— still burned an open. Worse, the accounting was generous in the wrong
direction in some paths. v0.4.4 increments max_opens only
after a successful decrypt, so the limit means what it says: number of
times this content was actually revealed.
In the same pass, seal_multi() — sealing one document to
multiple recipients — is disabled outright. Not deprecated, disabled. The
manifest format cannot yet honestly represent multiple recipients, and shipping a
multi-recipient seal whose manifest cannot describe what it did is exactly the
kind of silent dishonesty this release is about. It comes back when the manifest
can tell the truth about it.
Policy modes fail closed
Oversight's policy layer governs whether open-counting is authoritative locally
(LOCAL_ONLY), enforced by a registry (REGISTRY), or both
(HYBRID). The dangerous behavior was that REGISTRY and
HYBRID would, when the registry was unreachable, quietly fall back to
local state and keep going as if nothing were wrong. That turns a network outage
into a silent downgrade of the security model the issuer chose. v0.4.4 makes both
modes fail closed: if the registry that is supposed to be authoritative cannot be
reached, the open is refused, not approximated. LOCAL_ONLY counter
locking also now actually works on Windows, where the previous lock was a no-op.
Rekor verification rejects digest mismatches
Offline verification of a Rekor DSSE envelope is supposed to confirm that the logged attestation is about this content. The prior code verified the signature but did not insist that the envelope's subject digest match the expected content hash — so a validly signed attestation about a different document could be accepted against the wrong file. v0.4.4 rejects any DSSE envelope whose subject digest does not match the expected content hash. A signature over the wrong subject is not evidence about the right one.
The registry stops trusting unsigned material
Several registry fixes, all in the same spirit:
- Rekor attestations now use real watermark mark IDs and the manifest's actual
content_hash, instead of a placeholder likemark:<file_id>that recorded the right shape but the wrong fact. /registerrejects unsigned beacon and watermark sidecars that do not match the signed manifest. A sidecar that is not bound to the signed record is not part of the record.- Evidence bundles now include local transparency-log inclusion proofs for the recorded events, not just the signed tree head — so an auditor can verify that a specific event is actually in the log, not merely that a log exists.
- Registrations with no watermark are skipped for attestation rather than logged under a synthetic mark, because attesting to a watermark that was never applied is a false statement.
DNS beacons require a secret to be trusted
The DNS beacon path lets a recipient's environment phone home a passive lookup so
an issuer can see that a sealed file was opened. The hardening: beacon callbacks
now support a shared OVERSIGHT_DNS_EVENT_SECRET, and any non-loopback
callback fails closed when no secret is configured. An unauthenticated
callback from an arbitrary host is not an event worth recording, and pretending it
is would let anyone forge open-evidence.
Format adapters stop lying about success
Three format fixes, each closing a "reported success but did nothing safe" gap:
- The text adapter now applies L3 before L2/L1, matching the canonical core pipeline order instead of a divergent local order.
- DOCX metadata insertion no longer reports success when the
<cp:keywords>element is missing — it failed to write and said it succeeded. - PDF processing now rejects indirect
Launch,JavaScript, and unsafe URI actions before rewriting the file, so the watermarker does not faithfully preserve an active exploit primitive inside a document it just vouched for.
The empty tree gets its real root
The transparency log used an all-zero placeholder for the empty-tree root. RFC 6962
specifies the empty-tree Merkle root as SHA-256(""). v0.4.4 uses the
real value. This is the kind of fix that matters for exactly one reason: conformance
with auditors who verify against the spec rather than against my implementation.
Rust at parity
None of this is real until the Rust port does it too, because the conformance test
between Python and Rust is the ground truth this project is built on. So the Rust
workspace lands the same posture: oversight-container and
oversight-policy enforce max_opens only after a successful
recipient decrypt, REGISTRY/HYBRID fail closed instead of
falling back to local counters, and Rust seal_multi() fails closed
until recipient-honest manifests exist. oversight-rekor mirrors the
Python DSSE subject-digest rejection. oversight-registry requires the
DNS event secret for non-loopback callbacks and fails registration on malformed
signed artifacts instead of dropping them silently. oversight-formats
gets the same DOCX keywords and PDF action-rejection fixes. And the direct
rand dependency is gone in favor of rand_core::OsRng,
clearing the low-severity advisory path.
What v0.4.4 is not
v0.4.4 adds no features. It does not solve multi-recipient sealing — it disables it until the manifest can be honest. It does not make L3 collusion-resistant. It does not ship a GUI; that is the next release. What it does is take a tree that mostly worked and remove the places where it would rather succeed quietly than fail honestly. A provenance protocol earns trust by being conservative about what it claims, and the fastest way to lose that trust is a security model that downgrades itself the moment the network blinks. v0.4.4 is the release where Oversight stops doing that.