In CI, run pnpm sbom with an explicit --sbom-format, a pinned --sbom-spec-version, --sbom-type application for deployables, --prod for runtime scope and --out for a named file. Then add checks: file exists, component count above a floor, root component correct. Pin the pnpm version, too.
Why pin things
Defaults can change between versions. The pnpm documentation says the CycloneDX default spec version is 1.7 and that you may choose 1.5, 1.6 or 1.7 with --sbom-spec-version, a flag added in 11.1.0. If a downstream consumer reads just an older version, an unpinned job breaks the day a default moves. The --out flag arrived in 11.8.0, so the pnpm version itself belongs in your pinned toolchain.
The command shape
Use these flags together:
| Flag | Why in CI |
|---|---|
--sbom-format cyclonedx | The command requires a format |
--sbom-spec-version 1.6 | Match what consumers read |
--sbom-type application | Default is library, wrong for a service |
--prod | Dependencies and optional dependencies, runtime scope |
--out with a path | Predictable artifact name, may use %s and %v |
--sbom-supplier and --sbom-authors | Metadata for the reader |
The command needs a lockfile, so run it after a frozen install, or on a checkout that has the committed lockfile. Remember the optional --lockfile-just flag: it skips reading the store, which is faster but reflects the lockfile alone.
Steps for the job
- Pin the pnpm version in your CI image or setup step.
- Check out with the lockfile.
- Run the SBOM command with the flags above.
- Validate the JSON against the schema for the version you chose.
- Run sanity checks, listed below.
- Upload the file as a build artifact and attach it to the release.
Sanity checks worth adding
A file exists and is not empty. The root component name and version match package.json, since a missing manifest gives unknown and 0.0.0 per the docs. The component count is above a floor you set from the last good run. The type is application. If you set a supplier, it appears in metadata.
Fail the build on any of these. A silent empty SBOM is worse than a red job.
Peer dependencies
The docs add --exclude-peers, from 11.9.0, which drops peer dependencies and anything reachable just through them. It is meant for projects using automatic peer installation. Decide deliberately: excluding them shrinks the file but may hide packages that are installed in practice.
What to do after generation
Store the file with the release, name it with the version, and run a scan on it. Safeguard can scan SBOMs and dependencies, and the free scan at gold.safeguard.sh/scan is a quick way to see results on a first file.
A worked example
Here is a job outline for a repository with a single application package. The names and numbers are illustrative.
| Stage | Action | Failure means |
|---|---|---|
| Setup | Install the pinned pnpm version | Toolchain drift |
| Install | Run a frozen lockfile install | Lockfile is stale |
| Generate | Run pnpm sbom with format, spec version, type, prod scope and an output path | Command or flags broke |
| Validate | Check JSON against the schema for the chosen version | File is malformed |
| Sanity | Check root name, version, type and count floor | File is empty or wrong |
| Publish | Upload as artifact and attach to the release | Delivery step broke |
Suppose the previous release had 310 components. You set the floor at 250. A release that produces 12 is clearly wrong and the job fails. A release that produces 305 passes and you move on. The floor should be reviewed whenever the dependency set changes materially.
Writing the sanity checks
Use a small script, in any language the team already uses. It should read the JSON and assert four things. The root component name equals the package name. The root version equals the package version. The component type equals application. The component array length is at least the floor. Exit non zero otherwise, with a message that says which check failed.
Adding a diff step
Compare the new file against the previous release's file. Report added and removed components and changed versions. Many teams post the summary as a comment on the pull request. That turns the SBOM from a compliance artifact into a review aid, and it makes unexpected additions visible while they are cheap to fix.
Metadata worth setting
The authors and supplier flags write to the CycloneDX metadata block. Set them from a central place so every repository reports the same company name. Customers who ingest many files appreciate consistent supplier names.
Choosing production scope
With the production flag, the file describes what ships. Without it, dev tooling is included and marked as excluded scope in CycloneDX output. Pick one default for customer facing files and one for internal review, and name the files differently so they are never confused.
Caching and speed
The lockfile solely flag skips reading the store, which can speed up the job. If your install step already populated the store and you want the file to reflect it, leave the flag off. Document the choice. Whichever you pick, the file should be reproducible: running the job twice on the same commit should give the same components, apart from timestamps and serial numbers.
Failure handling
Make the SBOM job a required check on release branches. On feature branches, run it as informational so developers see the diff without being blocked. When it fails, the message should name the fix, such as running the install with the lockfile or updating the pnpm version.
Retention
Store each generated file with the release for as long as you support that release. When a new advisory lands, you want to search the files for versions you shipped, not just the latest build.
Try it yourself
Click through it at your own pace, or open it full screen.
Step through the pull request check and look at what makes the gate pass or fail.
Quick check
Why does my SBOM root say unknown 0.0.0?
There was no package manifest. The docs say the root is then unknown with version 0.0.0 and no license.
Should CI use --lockfile-just?
It is faster and deterministic from the lockfile, but it skips the store. Pick it deliberately and note it in your SBOM policy.
Should the SBOM job block merges?
On release branches, yes. On feature branches an informational diff is usually enough.
Sources
Last reviewed: 19 October 2026.