Skip to main content

Module integrity

Module integrity 

Source
Expand description

CLEANLIB-870 (integrity half) — artifact-bytes digest verification.

Status: the verification PRIMITIVE below is ready; there is nothing to wire it to yet. Confirmed live (2026-09-16) that GET /v1/fetch/... carries no digest-bearing response header at all — no ETag, no Content-MD5, no Digest, no x-goog-hash (the catalog is GCS-backed; a proxied GCS response would normally carry the latter, but the App does not pass it through). The signed attestation’s artifact_hash field is a hash of the serialized verdict, not of the artifact, and changes on every request — it structurally cannot serve as a content digest. So there is currently no real per-artifact digest anywhere on the wire for crate::transport::Client::fetch_artifact_stream to check the streamed bytes against. That is a server-side gap (filed separately per the ticket’s own “RELATED” section), not something this crate can close on its own by inventing a digest source.

What IS shippable now, independent of that blocker: this module’s verify_sha256_digest — a pure, tested comparison function — plus the CLI’s fetch command already writing to a same-directory temp file and only renaming it onto the real destination on full success (so any failure, a future digest mismatch included, leaves no partial output at the real path — see cleanlib-cli/src/commands/fetch.rs). The day a real per-artifact digest becomes available (a response header, most likely, matching how X-CleanLibrary-Decision/X-CleanLibrary-Reason are already read in transport.rs::emit_decision_headers for the sibling verdict-gating half of the same ticket), wiring it in is: read the digest, call verify_sha256_digest against the fully-streamed bytes before the CLI’s rename-into-place step, and fail closed (delete the temp file, non-zero exit) on DigestMismatch.

Structs§

DigestMismatch
The streamed bytes’ SHA-256 did not match the expected digest. Carries both hex-encoded digests so a caller can report exactly what diverged (never just “integrity check failed” with no evidence).

Functions§

verify_sha256_digest
Verify bytes against an expected SHA-256 digest, hex-encoded (lowercase or uppercase, with or without a leading sha256: prefix — both are real-world shapes a server might emit and rejecting on formatting alone would be its own false-negative bug). Returns Ok(()) on a match, DigestMismatch on any divergence — deliberately fail CLOSED: a malformed expected value (wrong length, non-hex characters) is NOT treated as “nothing to check” and passed through, it is compared byte-for-byte and will not match a real digest, which correctly surfaces as a mismatch rather than silently skipping verification.