To compare two SBOMs, list the components in each by their package identifier, then sort the differences into four groups: added, removed, upgraded, and changed in some other way, such as a different license or fingerprint. Then ask of each change: did we mean to do that?
TL;DR
- Compare by package identifier (such as a purl), not by display name.
- Sort changes into added, removed, version changed, and other changes.
- Every change should have a reason you can state in one sentence.
- A same version with a different fingerprint is the strangest case and deserves a question.
Definition. An SBOM diff is a comparison of two SBOMs for the same product, usually two releases, that lists what is different between them.
Why compare SBOMs at all?
Your software changes with every release. A new library comes in. An old one goes out. A version is bumped. If you only look at the final product, these changes are invisible.
The 2026 minimum elements guidance, published by CISA with partner agencies, says that each software version or update "should have an associated SBOM." It also says that when a build "integrates updated components or dependencies," a new SBOM should reflect the changes. So you will have a trail of SBOMs, one per release. Comparing neighbors in that trail shows how your supply chain moved.
Here are common reasons to do it.
- Review before release. Did anything unexpected sneak in?
- Answer a customer. "What changed since the version we run?"
- Investigate an incident. When did that package first appear?
- Check a supplier. Did their update touch what they said it would?
- Practice good hygiene. Notice dependency growth before it gets out of hand.
What parts of an SBOM should I compare?
Not every field is equally useful. Focus on a few.
| Field | Why it matters in a comparison |
|---|---|
| Package identifier (purl, CPE) | The stable key for matching the same package across files |
| Version | Shows upgrades and downgrades |
| Component hash value | Shows whether the actual file changed |
| License | Shows whether usage terms changed |
| Producer | Shows whether the package changed hands |
| Dependency relationship | Shows what pulls in what |
| SBOM version and timestamp | Shows which file is newer |
The 2026 guidance defines each of these elements. For example, the Component Hash Value is "the output generated from applying a cryptographic hash algorithm to an executable component artifact," which is a fingerprint of the file. The SBOM Version element "indicates a relationship with earlier iterations of an SBOM," and the SBOM Timestamp records the most recent update.
One more detail. The guidance says that when there are several instances of a component with different metadata, "each instance should be listed separately." So do not be surprised to see the same package name twice, with different versions.
How do I compare two SBOMs step by step?
- Confirm they are comparable. Same product, same format family, and check the SBOM version and timestamp so you know which is older. If one was made before the build and the other after, expect extra differences. The Generation Context field helps here.
- Extract the identifiers. Make a plain list of package identifiers from each file, one per line, sorted.
- Find what was added. Identifiers present in the new list but not the old.
- Find what was removed. Identifiers present in the old list but not the new.
- Pair up the upgrades. Match added and removed entries that have the same package name but different versions. These are upgrades or downgrades, not true additions and removals.
- Look at the pairs that stayed the same. For packages with the same identifier in both files, compare the hash, license and producer fields.
- Write a reason for every change. If you cannot, investigate.
- Save the result. Keep the comparison with the release notes.
What is a quick command line recipe?
If your SBOMs are CycloneDX files in JSON format and each component has a purl, you can do steps 2 to 4 with two common tools: jq, which reads JSON, and comm, which compares sorted lists. The layout of your own files may differ, so check the field names first.
jq -r '.components[].purl' old.json | sort > old.txt
jq -r '.components[].purl' new.json | sort > new.txt
echo "Added:"; comm -13 old.txt new.txt
echo "Removed:"; comm -23 old.txt new.txt
I ran this on two tiny sample files. A package upgraded from 1.0.0 to 1.0.1 showed up once in each list, which is step 5's cue to pair them up. Files with many components work the same way, only longer.
If there is no purl, use a combination of name and version, but expect more mismatches. See what a package URL is for why identifiers make this easier.
Worked example: a release review
Everything below is invented to show the process.
A team compares release 4.2 to release 4.3. After extracting identifiers, they find:
| Category | Invented count |
|---|---|
| Packages in release 4.2 | 312 |
| Packages in release 4.3 | 318 |
| Added | 11 |
| Removed | 5 |
| Of those, matched as upgrades (same name, new version) | 4 pairs |
| True additions | 7 |
| True removals | 1 |
That works out as follows. Eleven added minus the 4 upgrade partners leaves 7 new packages. Five removed minus 4 leaves 1 removed. Check the total: 312, plus 7, minus 1, equals 318.
The team writes a reason for each:
- 4 upgrades: planned security updates.
- 6 of the 7 new packages: arrive together with one new feature library.
- The seventh new package: nobody can explain it.
- 1 removal: a library deleted during cleanup.
The unexplained package is the finding. The team traces it through the dependency relationships and finds it came in with a version bump of a build tool. It is harmless, but now it is documented, and the next person who sees it will know why it is there.
They also compare hashes for the packages that did not change version. All match. If one had not, the team would have asked why a package with the same version had different contents.
What should make me suspicious?
Some changes deserve a second look.
- A new package with no clear reason. Always find the parent.
- A package that changed producer. Ownership changes are worth noting.
- A license change. Check the new terms with the right people. This post is not legal advice.
- The same version with a different hash. A version number should map to one file.
- Many NOASSERTION or unknown values appearing. The tool or settings may have changed. See what NOASSERTION means.
- A sudden jump in package count. Look for a large new dependency.
For a related read on keeping an eye on changes over time, see SBOM drift detection.
Try it yourself
In the demo below, watch how monitoring picks up a change after the first scan. Look for how a new finding is tied to a specific package and version.
Click through it at your own pace, or open it full screen.
How can Safeguard help?
Safeguard's SBOM and dependency scanning creates an SBOM for each project scan and exports in SPDX or CycloneDX, so you have comparable files for every release. Policies and gates can check pull requests as they change dependencies. Reachability analysis helps you judge whether a newly added package brings a risk your code can reach.
Try a free scan at gold.safeguard.sh/scan. The Safeguard CLI can run in your pipeline so each release gets a fresh SBOM, and it can also write code if you want help with a change.
Quick check
Should I compare SBOMs by package name?
Better to compare by a stable identifier such as a purl. Names can be written differently, and the same name can exist in more than one ecosystem.
How often should I generate a new SBOM?
CISA's 2026 guidance says each software version or update should have an associated SBOM, and a corrected one should follow when you find errors.
What does it mean if a package has the same version but a different hash?
The file contents changed even though the label did not. That is unusual and worth investigating before you ship.
Sources
- CISA and partners, 2026 Minimum Elements for a Software Bill of Materials (SBOM): https://www.cisa.gov/resources-tools/resources/2026-minimum-elements-software-bill-materials-sbom
- Package-URL (PURL) Specification: https://github.com/package-url/purl-spec
Last reviewed: 19 October 2026.