How to generate an SBOM from package-lock.json or NuGet in 10 minutes
You don't write an SBOM by hand — you generate it from your lock file with a single command. We show concrete commands for Syft, Trivy, `npm sbom` and CycloneDX .NET, output in both CycloneDX and SPDX, and what the CRA (Annex I Part II) actually requires: at least top-level dependencies in a common machine-readable format. It's not rocket science and you don't need a consultant.
An SBOM sounds like a big compliance task, but in practice it is one command and a few minutes. The Cyber Resilience Act requires it in Annex I, Part II, point 1 as part of the vulnerability-handling process — and prescribes no expensive tool for it. This guide is purely practical: exactly what to run for a Node.js and a .NET project so you end up with an SBOM in both common formats.
What the CRA asks of an SBOM (in brief)
So you know when you're done: Regulation (EU) 2024/2847 talks about at least the top-level (direct) dependencies in a commonly used, machine-readable format. It does not prescribe a specific standard — in practice this means CycloneDX or SPDX, the two established formats. You do not publish the SBOM: it is part of the technical documentation and is provided to a market surveillance authority on request. SBOM for small vendors covers this in detail; here we go straight to the commands.
Node.js / npm project
If you have a package-lock.json, you have three easy routes. The fastest is the built-in npm command (from npm 10):
- Built into npm:
npm sbom --sbom-format cyclonedx > sbom.cdx.json— produces CycloneDX directly from the lock file, with nothing extra to install. For SPDX use--sbom-format spdx. - Syft (open source, Apache-2.0):
syft scan dir:. -o cyclonedx-json=sbom.cdx.json -o spdx-json=sbom.spdx.json— produces both formats in a single run. It can also read the lock file directly:syft scan file:package-lock.json -o cyclonedx-json. - Trivy (open source, Apache-2.0):
trivy fs --format cyclonedx --output sbom.cdx.json .— Trivy can additionally compare that same scan against vulnerability databases right away.
Find out whether the CRA applies to you
Six questions, two minutes, no account. Get your product class and first steps.
.NET / NuGet project
For .NET, the most direct route is the official CycloneDX tool:
- Install:
dotnet tool install --global CycloneDX(once). - Generate:
dotnet CycloneDX MyApp.sln -j -o .— walks the solution or project and produces CycloneDX (-j= JSON) into the current folder. - Syft alternative:
syft scan dir:. -o cyclonedx-json=sbom.cdx.jsonalso works overpackages.lock.json(if you haveRestorePackagesWithLockFileenabled).
If you don't yet have a lock file, in .NET you enable it via RestorePackagesWithLockFile in the .csproj and dotnet restore creates one — thanks to it your SBOM will contain exact resolved versions, not just manifest ranges.
A few habits so the SBOM actually serves you
Generating the file is the start; it earns its keep when it is current and traceable:
- Version the SBOM with every release. Each product version has its own component list — otherwise you can't tell which shipped version is vulnerable.
- Put generation in CI. One pipeline step that emits
sbom.cdx.jsonas a build artifact means you never forget. - Keep it with the documentation. Under the CRA, technical documentation is retained for at least 10 years from placing on the market (or for the support period, if longer) — the SBOM belongs with it.
- Don't stop at top-level. The minimum is direct dependencies, but Syft and Trivy also capture transitive ones — and those are what most often surprise you during the first incident.
What an SBOM alone does not solve
An SBOM is a list, not a guard. On its own it won't tell you a new vulnerability appeared in a component — you learn that only when you compare it against databases (OSV.dev, NVD, CISA KEV). And that comparison is recurring, not one-off work: new CVEs appear daily. How to read the acronyms (CVE, CVSS, EPSS, KEV) is covered in a separate article.
FAQ
- CycloneDX or SPDX? The CRA doesn't prescribe; both are "commonly used, machine-readable". Syft and Trivy can produce both at once, so make both.
- Is
package.json/.csprojenough? No — a manifest lacks exact resolved versions and transitive dependencies. Generate from the lock file or build. - How often to regenerate the SBOM? At every release; ideally automatically in CI so you don't forget.
- Must we publish the SBOM? No — it's part of the technical documentation and is provided to an authority on request.
What to watch out for
Two mistakes recur. First: generating an SBOM once "for show" and letting it go stale — at the first incident it then doesn't match the shipped version. Second: relying on the manifest instead of the lock file, so the SBOM lacks exact versions and monitoring against databases is unreliable. Both are solved by one habit: generate the SBOM from the lock file automatically at build time and version it with the release. Then the SBOM is always current and usable for the most important thing — a fast "does this new CVE affect us?".
Bottom line
An SBOM isn't mountains of paperwork but one command and a few minutes. Annex I Part II asks for it as part of vulnerability handling — at least top-level dependencies in a common machine-readable format (CycloneDX or SPDX). Node.js project: npm sbom, Syft or Trivy from the lock file. .NET: dotnet CycloneDX or Syft over packages.lock.json. Container or monorepo? Same tools, different scan target. Key habits: generate from the lock file (not the manifest), put it in CI and version the SBOM with every release. But an SBOM is just a list, not a guard — it earns its keep only when you continuously compare it against databases (OSV, KEV, EPSS). That's recurring work, because new CVEs arrive daily — and automating that comparison is what decides whether the "does it affect us?" answer takes moments or hours.
Key point
An SBOM isn't a mountain of paperwork — it's one command and a few minutes. Annex I, Part II requires it as part of vulnerability handling — at minimum the top-level dependencies in a common machine-readable format (CycloneDX or SPDX). Node.js: npm sbom, Syft or Trivy from the lock file. .NET: dotnet CycloneDX or Syft over packages.lock.json. A container or monorepo? Same tools, a different scan target. Key habits: generate from the lock file (not the manifest), put it in CI, and version the SBOM with every release. But an SBOM is only a list, not a guard — it earns its keep once you continuously compare it against databases (OSV, KEV, EPSS). That's recurring work, because new CVEs appear daily — and automating that comparison is what decides whether the answer to "does this affect us?" takes seconds or hours.
How CRAguard helps
CRAguard takes your lock file, generates an SBOM from it (both CycloneDX and SPDX), keeps it versioned with every release, and compares it daily against OSV, KEV and EPSS — so the answer to "does this new CVE affect us?" is ready before the 24-hour Article 14 clock starts. Find out whether the CRA applies to you, or join the waitlist — we'll let you know at launch.
This article is informational and not legal advice. It is based on Regulation (EU) 2024/2847 (Annex I Part II) and the European Commission's guidance of 27 July 2026. The tools named are third-party; verify commands against the current version of each tool.
Find out whether the CRA applies to you
Six questions, two minutes, no account. Get your product class and first steps.