Provetry opens in November 2026. The free CRA check, the tools and the guides are available now.

SBOM for Go modules and binaries under the CRA

6 min read

On this page
  1. go.mod is (almost) the lock file
  2. Generate the SBOM
  3. The binary describes itself
  4. The standard library is a component
  5. govulncheck: is the vulnerable code called?
  6. How Provetry helps
  7. Sources

The Cyber Resilience Act asks every manufacturer to keep a software bill of materials “in a commonly used and machine-readable format covering at the very least the top-level dependencies” of the product (Annex I, Part II, point 1). Go makes most of this easy: modules have exact versions, and every binary carries the list of modules it was built from. It also has an official, free tool for telling whether a vulnerability actually reaches your code.

For the regulation itself, see SBOM for the Cyber Resilience Act.

go.mod is (almost) the lock file

Since Go 1.17, go.mod lists every module needed to build the main module’s packages, direct requirements first and indirect ones marked // indirect, at exact versions. go.sum only holds checksums. Two things to watch:

  • replace directives swap a module for a fork or a local directory. The SBOM must name what is actually built, not the original module.
  • More is listed than is built. go.mod covers every package of the module, tests and tools included. A binary built from ./cmd/server may use far fewer modules.

Generate the SBOM

cyclonedx-gomod from the CycloneDX project has three modes. For a product, use app, which follows only the packages of the application you build:

go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest
GOOS=linux GOARCH=amd64 cyclonedx-gomod app -json -std -main cmd/server -output sbom.cdx.json .

Set GOOS, GOARCH and GOFLAGS=-tags=... to the values of your release build, since build constraints change which modules are compiled in. -std adds the Go standard library as a component (see below). mod describes the whole module, which suits a library; bin reads a compiled binary.

The binary describes itself

Every Go binary built with modules embeds its build information:

go version -m ./server

prints the Go version, the main module and every dependency with its version and checksum. This is how scanners such as Syft, Trivy and govulncheck itself (-mode binary) can describe or check a Go binary found in a container image or on a device, without the source.

The standard library is a component

Many Go vulnerabilities are in the standard library: net/http, crypto/x509, html/template, os/exec. Your binary contains the standard library of the Go version that built it, so that version is part of your product and must be in the SBOM. Build releases with a supported Go version (each major release, such as 1.25, is supported until two newer ones are out), set the toolchain line of go.mod, and build with GOTOOLCHAIN set to that exact version, or in a pinned builder image, so every build uses the same one: with the default GOTOOLCHAIN=auto, a newer local Go wins. go version -m records the version actually used.

govulncheck: is the vulnerable code called?

The Go team’s govulncheck builds the call graph of your program and reports only the vulnerabilities whose vulnerable functions your code can actually reach:

go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
govulncheck -format openvex ./... > reach.vex.json

The OpenVEX output records, for each known vulnerability in the module versions you build with, whether your code is affected. It is the kind of evidence the CRA’s vulnerability handling calls for. Calls through reflection, plugins or cgo are not seen, and it analyses one platform and set of build tags per run. Look first at the vulnerabilities exploited in the wild, and see What is VEX? for how statements are read.

How Provetry helps

Provetry reads go.mod, with its replace directives (so its SBOM may also list modules used only by tests or tools), from your CI or from the files you upload, scans the Go version of the toolchain line for standard library advisories, writes the SBOM of each release in CycloneDX, and in SPDX on paid plans, and watches every module for new and exploited vulnerabilities, every day on paid plans and every week on Free. Its CI step runs govulncheck in every Go module of the repository: vulnerabilities your code never calls are suggested as “not affected”, with govulncheck’s evidence; those it calls move up the list. If govulncheck fails in a module, nothing that module depends on is declared not affected. These are the only results a CI gate relies on.

Sources