Most SBOM guidance stops at the point where you have generated a file. That is the easy part, and it is not the part that determines whether the document is worth anything.
An SBOM is a claim about what is inside your software. The useful question is whether that claim is true, and for a large share of generated SBOMs it is only partially true.
This covers the formats, where in the pipeline to generate, which tools to use, and the specific categories of component that a build-generated SBOM structurally cannot see.
Where the gap gets closed: CodeAnt AI works from the artifact as well as the source, mining shipped bundles directly and correlating findings against the National Vulnerability Database, the CISA Known Exploited Vulnerabilities catalog, and EPSS. The declared-versus-actual gap described below is the reason that matters.
What is a software bill of materials?
A software bill of materials, usually shortened to SBOM, is a formal, machine-readable inventory of the components that make up a piece of software, along with the relationships between them.
The analogy to a physical bill of materials is exact and worth holding onto.
A manufacturer knows every part in a product, who supplied it, and which sub-assembly it belongs to. When a part is recalled, they can identify every affected unit within hours.
Software historically had no equivalent. When a widely used library turned out to be critically vulnerable, the answer to "are we affected" required a manual audit at most organisations.
The NTIA minimum elements
Following United States Executive Order 14028 in May 2021, the National Telecommunications and Information Administration published a set of minimum elements.
A document must contain all of them to be considered an SBOM.
Element | What it records |
|---|---|
Supplier name | Who produced the component |
Component name | What the component is called |
Version | Which version is included |
Other unique identifiers | CPE, PURL, or similar for machine matching |
Dependency relationship | Which component includes which |
Author of SBOM data | Who generated this document |
Timestamp | When it was generated |
Two of those do more work than the rest.
Unique identifiers are what make automated vulnerability matching possible at all, and dependency relationships are what let you distinguish a direct dependency you chose from a transitive one you inherited.
An SBOM listing component names and versions with no identifiers is a document a human can read and a machine cannot use.
CycloneDX vs SPDX, which format to use
Two formats dominate. A third exists and is rarely the right answer for new work.
CycloneDX | SPDX | |
|---|---|---|
Steward | OWASP | Linux Foundation |
Standardised as | ECMA-424 | ISO/IEC 5962:2021 |
Original focus | Security and vulnerability use cases | Licence compliance |
Encodings | JSON, XML, Protobuf | JSON, YAML, RDF, tag-value, spreadsheet |
Vulnerability data | Native support, integrated VEX | Handled through separate documents |
Typical feel | Compact, security-first | Comprehensive, compliance-first |
The practical guidance is short.
Choose CycloneDX if your primary use case is security, if you want vulnerability and exploitability data in the same document, or if you want the smallest workable file.
Choose SPDX if licence compliance is the driver, if you are in a procurement chain that specifies ISO conformance, or if a regulator or customer asks for it by name.
Choose both if you are shipping to enterprise customers, because conversion between them is a solved problem and receiving teams have preferences you cannot predict.
The format argument is genuinely less important than the content argument. A complete SBOM in either format beats an incomplete one in your preferred format.
Where VEX fits
VEX stands for Vulnerability Exploitability eXchange. Always write it out, because the three-letter abbreviation collides badly with an unrelated robotics brand in search.
A VEX document answers a question an SBOM cannot. Your SBOM says a vulnerable library version is present. VEX says whether that vulnerability actually affects your product.
Four statuses carry the meaning.
Not affected means the vulnerable code path is not reachable in your product, and the statement should carry a justification such as the vulnerable function never being called.
Affected means it is exploitable and remediation is required. Fixed means it was addressed in this release. Under investigation means you have not yet determined which.
VEX matters because SBOM adoption creates a downstream problem.
Once customers can see your dependencies, they will file tickets about every CVE affecting any listed version, regardless of reachability. VEX is how you answer at scale rather than one email at a time.
How SBOMs are generated, and at which build stage
Generation point determines completeness, and this is the decision that most affects SBOM quality.
Stage | Sees | Misses |
|---|---|---|
Source | Declared manifests and lockfiles | Anything resolved or injected during build |
Build | Resolved dependency graph, build-time inputs | Prebuilt binaries pulled in as opaque artifacts |
Binary | What is actually in the shipped artifact | Nothing structurally, but detection is probabilistic |
Runtime | What is actually loaded and executed | Code paths not exercised during observation |
Source-stage generation reads package.json, pom.xml, go.mod, Cargo.toml, or the equivalent. It is easy, fast, and it describes intent rather than outcome.
Build-stage generation hooks into the build system and records what was actually resolved and linked. This is the highest-fidelity option for the code you compile, and it is the recommended default.
Binary-stage generation derives the inventory from the finished artifact. It catches everything that reached the artifact regardless of how, which is exactly what the earlier stages miss.
Runtime observation tells you what is actually loaded, which is a different and complementary signal. A component present but never loaded is a lower risk than one on your primary request path.
The mature position is to generate at build stage and validate against a binary-derived inventory, because the difference between the two is a list of things you did not know you shipped.
SBOM tools compared
The tooling has consolidated considerably. These are the options worth knowing.
Tool | Type | Formats | Best for |
|---|---|---|---|
Syft | Open source | CycloneDX, SPDX | Containers and filesystems, the common default |
Trivy | Open source | CycloneDX, SPDX | Combined SBOM generation and vulnerability scanning |
cdxgen | Open source | CycloneDX | Broad language coverage, many ecosystems |
CycloneDX plugins | Open source | CycloneDX | Native build integration for Maven, Gradle, npm |
SPDX tools | Open source | SPDX | Validation and format conversion |
Tern | Open source | SPDX, CycloneDX | Container layer inspection specifically |
ORT | Open source | SPDX, CycloneDX | Licence compliance workflows |
Dependency-Track | Open source | Consumes both | Continuous monitoring of SBOMs over time |
Commercial | One-click export, PDF for audits | Build-stage generation plus continuous re-evaluation against NVD, KEV, and EPSS |
One distinction is worth drawing clearly. Most of that list generates SBOMs once, as a standalone artifact. Dependency-Track and CodeAnt both consume and monitor them continuously against new vulnerability data.
That second capability is the one teams skip and then regret. An SBOM generated at release time and filed away is a snapshot.
New vulnerabilities are disclosed against components in it every week, and something has to re-evaluate it.

What a build-generated SBOM cannot see
This is the section that matters most, and it is missing from almost all SBOM guidance.
A build-stage SBOM records what the build system knows about. Five categories of component reach your artifact without the build system recording them.
Statically linked libraries. A C or C++ dependency compiled directly into your binary leaves no package manager record. The code is present, it may be vulnerable, and no manifest mentions it.
Vendored code. Source copied into your repository rather than declared as a dependency. Common for small utilities and for patched forks. It appears to your tooling as first-party code.
Files copied into container images. A
COPYinstruction places a binary in the image with no package metadata. Package-manager-based scanning is blind to it by construction.Build-time injected assets. Bundled JavaScript, embedded resources, and generated code that the bundler pulled in from somewhere your manifest does not describe.
Transitive native dependencies. A package that ships prebuilt native binaries brings whatever those binaries were compiled against, and that inner layer is opaque to the outer manifest.
The common property is that all five are present in the artifact and absent from the manifest. A vulnerability in any of them is real, exploitable, and invisible to any tool reading your lockfile.
Binary composition analysis and the binary SBOM
Binary composition analysis derives the component inventory from the compiled artifact rather than from the build metadata. It is the only method that closes the five gaps above.
The detection techniques are probabilistic rather than authoritative, which is the honest trade-off.
Version strings. Most libraries embed a version banner in their compiled form. A single string frequently identifies an exact release.
Function signatures. A compiled function has a recognisable shape. Matching against a corpus of known library builds identifies components that embed no strings at all.
Constant tables. Cryptographic implementations contain distinctive constant arrays. S-boxes and initialisation vectors act as fingerprints.
Build artifacts. Compiler version strings, embedded build paths, and residual debug metadata frequently survive and often disclose more than intended.
The workflow follows the same structural-diffing family of techniques used against binary patches, matching on control-flow shape and reference tables rather than on raw bytes.
The output is a binary SBOM, and its relationship to a build-generated one is the point.
Build-generated SBOM | Binary SBOM | |
|---|---|---|
Derived from | Build system metadata | The shipped artifact |
Accuracy on declared deps | Authoritative | Probabilistic |
Statically linked code | Invisible | Detected |
Vendored code | Invisible | Detected |
Requires build access | Yes | No |
Works on third-party software | No | Yes |
Neither is a replacement for the other. Build-generated is authoritative for what you declared. Binary-derived is the only option for what you did not declare and for software you did not build.
How to reconcile a declared SBOM against a binary-derived one
The reconciliation is the exercise, and it is more valuable than either document alone.
Step 1. Generate both. Build-stage SBOM from your pipeline, binary-derived inventory from the shipped artifact.
Step 2. Diff them. Three categories of discrepancy emerge, and each means something different.
In binary, not in SBOM. This is the important one. Every entry is a component you ship and did not declare, which means no vulnerability feed has ever been matched against it.
In SBOM, not in binary. Usually benign. Tree-shaking, dead code elimination, or a build-time-only dependency. Worth confirming rather than assuming.
Version mismatch. The manifest says one version and the artifact contains another. Frequently caused by a transitive resolution you did not expect, and always worth investigating.
Step 3. Match the reconciled inventory against vulnerability data. Use the reconciled list, not the declared one, when querying CVE data. CodeAnt's vulnerability database is built for this, keeping the query closer to the vendor advisory than to the slower, enriched NVD record.
Step 4. Prioritise the results. Component presence is not risk. Apply CVSS, EPSS, and CISA KEV together, the way CodeAnt's EPSS scoring does for both SAST and SCA findings, rather than triaging on severity alone.
Step 5. Publish VEX for the false positives. Once you have established that a listed vulnerable component is not reachable, say so formally rather than answering the same customer question repeatedly.
SBOM requirements under current regulation
Three regimes drive most SBOM demand. Verify current status directly, since requirements have been evolving quickly.
United States federal procurement. Executive Order 14028, issued May 2021, directed the establishment of SBOM requirements for software sold to the federal government, and the NTIA minimum elements followed in July 2021.
Medical devices. The United States Food and Drug Administration requires cybersecurity information including a software bill of materials in premarket submissions for devices with software.
European Union. The Cyber Resilience Act entered into force in December 2024 and introduces obligations for products with digital elements, including maintaining a software bill of materials. Its main obligations phase in over subsequent years.
The common direction of travel is toward SBOM as a procurement precondition rather than a security nicety.
Practically, that means the document will be read by people who did not write it, so accuracy has an external consequence.
How CodeAnt AI Fits Into the Stack
Two capabilities, on two different sides of the declared-versus-actual gap.
Generating the declared side properly. CodeAnt's SBOM feature is build-integrated rather than a one-off script: it detects every package and version across branches, flags restrictive or unknown licences before they become a compliance problem, and exports an audit-ready PDF on demand.
That covers the source and build stages described above, done continuously rather than at release time, which is what makes reconciliation against a binary inventory possible in the first place instead of comparing a fresh artifact scan against a stale document.

Closing the artifact-side gap. The AI penetration testing pipeline performs artifact-level analysis on the web surface, mining production JavaScript bundles for API keys, internal hostnames, OAuth client identifiers, and source-map artifacts.
That is the same declared-versus-actual gap this article describes, applied to the bundled surface and run continuously rather than at release time. It does not replace the build-stage SBOM above, it is the binary-side check the reconciliation section describes, run against your own shipped frontend.
Which listed components actually matter. Attack surface management correlates the reconciled inventory against the National Vulnerability Database, the CISA Known Exploited Vulnerabilities catalog, and EPSS scoring, refreshed daily.
An SBOM produces a list of components. That correlation is what turns the list into a shortlist, using EPSS to filter the improbable mass and the CISA KEV catalog to define urgency.
On the value of looking beyond declared dependencies, the record is public.
CodeAnt's research team has disclosed 100+ CVEs, including CVE-2026-29000, a CVSS 10.0 authentication bypass in pac4j-jwt, and CVE-2026-28292, a CVSS 9.8 remote code execution bug in simple-git that had survived two earlier patch attempts, across packages that together sit in the dependency tree behind a share of the 1.85B+ monthly downloads CodeAnt's research has audited. For as long as either bug went unfixed, any SBOM listing that library reported it clean, because no advisory had been filed against it yet. That is exactly the gap a document, on its own, cannot close.
The SBOM completeness checklist
Generation
Generate at build stage, not from source manifests, so the resolved graph is captured.
Include unique identifiers such as PURL or CPE for every component. Names and versions alone are not machine-matchable.
Record dependency relationships, so direct and transitive components are distinguishable.
Completeness
Audit for statically linked libraries that carry no package record.
Audit for vendored source copied into the repository rather than declared.
Audit container images for files added by
COPYwith no package metadata.Check packages shipping prebuilt native binaries for what those binaries were compiled against.
Reconciliation
Derive a binary inventory from the shipped artifact and diff it against the declared SBOM.
Investigate every in-binary-not-in-SBOM entry. Each is an undeclared shipped component.
Investigate version mismatches before assuming the manifest is correct.
Ongoing use
Monitor SBOMs continuously, not at release only, since new vulnerabilities land against existing components weekly.
Publish VEX statements for components where the vulnerable path is not reachable.
Match against the reconciled inventory, never against the declared one alone.
Where this leaves you
Generating an SBOM is now a solved problem with good free tooling. Generating an accurate one is not.
The distinction that matters is between the components you declared and the components you shipped.
Those two lists differ at almost every organisation, and the difference is precisely the set of components no vulnerability feed has ever examined.
Generate at build stage. Reconcile against the artifact. Then treat the difference as your actual finding list.
Related reading on CodeAnt AI: Zero-Day vs N-Day, and the Patch Window for what happens after a component you tracked turns out to have a public fix you have not deployed, and the IDOR guide for the authorization-layer bug class that sits alongside dependency risk in the same shipped artifact.


