SBOM drift is the divergence between what a software bill of materials documents and what the corresponding artifact actually contains. The SBOM says openssl 3.0.8; the running container has 3.0.12 because the base image was rebuilt. Or the SBOM was generated from package.json while the deployed bundle includes a dependency that a postinstall script fetched at build time. An SBOM is a snapshot of one build at one moment, and everything that happens after that moment — rebuilds, base image updates, hotfixes, floating tags — pulls the artifact away from the document. A drifted SBOM is worse than no SBOM, because people make incident-response and compliance decisions based on a list that is quietly wrong.
Where drift comes from
Drift is rarely malicious. It falls out of normal build mechanics:
- Floating references.
FROM node:20orFROM ubuntu:latestresolves to a different image digest next month. Same Dockerfile, different 400 packages. Any SBOM generated last month no longer describes this month's rebuild. - Unpinned dependency ranges. A
requirements.txtwithrequests>=2.28or an npm range like^4.17.0means two builds a week apart can produce different trees. The SBOM describes whichever tree existed on generation day. - SBOMs generated from the wrong input. Scanning the source manifest instead of the built artifact misses everything the build itself introduces: vendored code, compiled-in static libraries, files fetched by
curlin a DockerfileRUNstep, packages installed by a postinstall hook. - Post-build mutation. Someone
kubectl execs into a pod and installs a debugging tool, or a hotfix is applied directly to a long-lived VM. The artifact changed; no build ran; no SBOM was regenerated. - Registry tag reuse. Pushing a new image over an existing tag (
myapp:1.4.2) while the SBOM store still associates that tag with the old build. Content-addressing by digest exists precisely to prevent this.
The polyfill.io incident in June 2024 is the CDN-flavored version of the same problem: what pages actually loaded diverged from what anyone had reviewed, because the reference was floating and the content behind it changed ownership.
Why it matters more than it sounds
The whole point of an SBOM is answering "are we affected?" in minutes instead of days. When Log4Shell dropped, organizations with accurate SBOMs grepped for log4j-core and had an answer before lunch. With a drifted SBOM you get a confident wrong answer — arguably worse than the honest "we don't know," because it ends the investigation.
Compliance raises the stakes. Executive Order 14028 and the follow-on OMB M-22-18 attestation requirements push vendors to provide SBOMs to US federal buyers; the FDA now expects SBOMs in premarket submissions for medical devices. An SBOM you hand to a customer is an assertion about the product. If an auditor or a customer's security team regenerates one from your artifact and gets a materially different list, you have a credibility problem, not just a tooling one.
Detecting drift: regenerate and diff
Detection is conceptually simple: generate a fresh SBOM from the artifact as it exists now, and diff it against the stored SBOM from build time.
# Stored at build time
syft registry:myco/api@sha256:3f1b... -o cyclonedx-json > sbom-build.json
# Regenerated now, from the same digest (or the running image)
syft registry:myco/api:1.4.2 -o cyclonedx-json > sbom-now.json
# Diff at the component level
sbom-utility diff --input-file sbom-build.json --input-revision sbom-now.json
If the diff at the same digest is non-empty, your SBOM generator changed behavior between runs (tool version drift is its own category — pin your syft version in CI). If you're diffing across a tag rather than a digest and the diff is non-empty, the tag was rebuilt or overwritten, and every consumer of the old SBOM is misinformed.
For running workloads, compare the SBOM against what a runtime agent or an in-cluster scan actually observes. The delta between "documented," "present," and "loaded at runtime" is also the basis for reachability filtering in SCA tooling — the same machinery that tells you a vulnerable package isn't loaded can tell you an undocumented package is.
We covered automation patterns for this in more depth in automated SBOM drift detection.
Preventing drift structurally
You can't prevent artifacts from changing; you can make every change produce a fresh, correctly-associated SBOM.
- Generate the SBOM in the build, from the artifact. Not from source manifests, not in a nightly batch job. The SBOM step runs after the image is built, against the image:
syft <image-digest>as a CI stage, or BuildKit's native--attest type=sbomondocker buildx build. - Key SBOMs by digest, never by tag. Store and retrieve by
sha256digest. Attach the SBOM to the image as an attestation (cosign attest --predicate sbom.json --type cyclonedx <digest>) so the association is cryptographic rather than a row in a database that can go stale. - Pin your inputs. Base images by digest, lockfiles committed and enforced (
npm ci,pip install --require-hashes,poetry.lock). Pinning doesn't stop drift across intentional rebuilds, but it makes builds reproducible enough that drift becomes a signal instead of noise. - Make post-build mutation an event. Immutable infrastructure policies (read-only root filesystems, no
execin production namespaces) turn "someone changed the artifact without a build" from routine into an alert. - Rescan stored SBOMs against new advisories. This isn't drift prevention, but it's the companion discipline: the artifact may be unchanged while the world's knowledge about it changes. A drift-free SBOM pipeline plus continuous re-analysis — the model behind SBOM Studio — covers both directions.
A quick maturity check
| Level | Practice | Drift exposure |
|---|---|---|
| 0 | No SBOMs, or one-off exports on request | Total — every answer is archaeology |
| 1 | SBOM per release, generated from manifests | High — build-time additions invisible |
| 2 | SBOM per build, generated from artifact, stored by tag | Medium — tag overwrites still lie |
| 3 | SBOM per build, keyed by digest, attached as signed attestation | Low — drift is detectable and attributable |
| 4 | Level 3 plus runtime reconciliation and scheduled diffs | Minimal — drift raises an alert within hours |
Most organizations we talk to sit at level 1 and believe they're at level 3. The regenerate-and-diff exercise above takes an afternoon and settles the question.
Frequently asked questions
Is SBOM drift the same as configuration drift?
Same shape, different object. Configuration drift is running systems diverging from their declared infrastructure-as-code definitions; SBOM drift is artifacts diverging from their declared component inventory. The remedies rhyme too: immutable references, regenerate-and-diff, and treating out-of-band mutation as an incident.
How often should I regenerate SBOMs to catch drift?
Regenerate on every build automatically — that's the baseline, not a schedule. For long-lived artifacts that rarely rebuild, a weekly scheduled diff between stored SBOM and current artifact state catches mutation and tooling drift. Anything less frequent than your deploy cadence guarantees blind spots.
Does a signed SBOM prevent drift?
No — signing proves the SBOM wasn't tampered with after generation and binds it to a specific artifact digest. The artifact behind a floating tag can still change. Signing plus digest-keying makes drift detectable and non-repudiable; it doesn't make it impossible.
Which SBOM format handles drift better, SPDX or CycloneDX?
Neither format prevents drift; both are snapshots. CycloneDX's BOM serialNumber and version fields plus tooling like sbom-utility make revision diffing slightly more ergonomic, while SPDX 2.3 relationships express build lineage well. Pick the format your consumers require and invest in the pipeline instead.