Supply-chain case study · retrospective
xz-utils · March 2024CVE-2024-3094 — the most patient supply-chain attack on record. A persona built credibility as a co-maintainer of liblzma over two years, then planted an SSH backdoor that lived only in the release tarball — the git source tree was clean. Caught by accident: an SSH login that was 500 ms slower than it should have been.
Attack chain
xz-5.6.0 and 5.6.1 ship with an obfuscated payload embedded in test fixtures — binary files that are part of the tarball but not in the git tree. Build-time M4 macros conditionally splice the payload into liblzma.so during dist builds. The structural gap
Traditional CVE-detection scanners (Trivy, Grype, OSV) compare an artifact's declared version against a database of known-bad versions. For Shai-Hulud-class attacks where the bad code lands in a numbered release of a known package, that loop closes within hours of disclosure.
For xz-utils, that loop didn't close for two years. The poisoned tarballs were the official releases, signed by the legitimate signing key, with no GHSA advisory and no CVE entry. Any CVE-scanner pulling fresh tarballs in early-to-mid March 2024 would have given them a green light.
The only place the attack was visible was at the boundary between source and binary — and that's a different kind of gate entirely. Not "does this artifact match a known-bad version?" but "does this binary actually match the source that claims to produce it?"
The OrbitalReg defense pattern
Every artifact stored in OrbitalReg carries its CI run, commit
SHA, builder identity, and the upstream source URL. When a new
version of xz-5.6.0 lands, the registry records "this came
from upstream tarball at hash X,
which the maintainer published while their Sigstore identity
was Y." Provenance
anchors the artifact in the chain of custody.
For artifacts where upstream commits to reproducible-build guarantees (Debian, NixOS, recent Rust, recent Go), OrbitalReg can be configured to rebuild from source and refuse to serve any binary whose hash differs. An xz-5.6.0 tarball that splices in extra bytes at build time would not match the deterministic rebuild — gate trips, binary quarantined.
Pull-gate can require not just any Sigstore signature but a specific Fulcio-issued identity tied to a known upstream account. If the signing identity for the project rotates without warning — or if a new identity appears on the release — the gate refuses to serve.
Config sketch
OS-package repos with upstream reproducible-build guarantees can be gated on rebuild verification. Slower than CVE-only, but catches a class of attack CVE-scanning structurally can't.
# Mirror Debian main, require provenance + rebuild verification.
orbital repo create debian-main \
--format=debian --kind=remote \
--upstream=https://deb.debian.org/debian \
--scanner=trivy,osv \
--require-provenance=build-info \
--reproducible-build=verify-or-quarantine
Reproducible-build verification is optional, off by default. Enable per-repo where upstream supports it. Full grammar at docs.orbitalreg.com/guide/pull-gates.
For your records
Honest caveats
We would not have caught Jia Tan's social engineering. By March 2024 he was a legitimate co-maintainer with valid signing keys. Sigstore-identity gates pass when the identity is the one on file. The defense isn't "stop bad maintainers from existing" — it's "raise an audit signal when their artifacts diverge from their source."
Reproducible-build verification needs upstream cooperation. The gate only works for packages where upstream actually ships a reproducible build (Debian main, NixOS, recent Go, recent Rust). For packages where upstream tarballs are not deterministic, the gate cannot run and has to be turned off. Many ecosystems are not there yet.
Rebuild verification is expensive. Reproducible builds take real compute. We recommend running them on a delay queue (e.g. all new pulls run async within 24 h), with the artifact served from cache in the meantime and quarantined if the delayed rebuild diverges. That's a 24-hour window where bad code can still flow. Not zero risk — substantially less risk.
Andres Freund got lucky. The window between xz-5.6.0 release and disclosure was about five weeks. A provenance gate doesn't shrink that window to zero. What it does is: change the discovery mechanism from "a postgres developer notices SSH is slow" to "the registry refused to serve a binary that didn't match its source, two days after upload."
Primary sources
Want this configuration for your team?
Rico, the founder, walks you through which of your registries can support reproducible-build verification today, which need a manual quarantine workflow as a stopgap, and the realistic time-to-value for each.