Safeguard
Developer Tools•

Does a Go vendor directory change what your SBOM should trust?

When a Go repo has a vendor directory, builds read it instead of the module cache. Here is what that means for SBOM generation and which file to check.

Safeguard Security Team
6 min read

If a Go repository has a vendor directory and its go.mod declares Go 1.14 or higher, the go command builds from vendor by default. So the build inputs are the vendored copies, and vendor/modules.txt records the module versions. Check that your SBOM matches that manifest, not just go.mod.

What vendoring does

The Go modules reference says go mod vendor creates a vendor directory in the main module root. It holds copies of the packages needed to build and test the main module. It also writes vendor/modules.txt, a list of vendored packages and the module versions they came from.

When vendoring is on, commands such as go build and go test load packages from vendor instead of using the network or module cache. By default that happens when vendor exists and the go version in go.mod is 1.14 or higher. You can force it with -mod=vendor, or turn it off with -mod=readonly or -mod=mod.

Why this matters to an SBOM

An SBOM tool can read go.mod, read vendor/modules.txt, or inspect a built binary. In a healthy repo all three agree. They can disagree when someone edited go.mod and forgot to rerun the vendor command. The Go reference says the go command checks vendor/modules.txt against go.mod and reports an error if go.mod changed since vendoring.

That check is useful to you. A failed build is a loud signal. An SBOM generator that reads just go.mod would quietly describe a different set of versions than the code that was compiled.

A simple decision guide

SituationWhich source to trust
No vendor directorygo.mod and go.sum
Vendor present, go 1.14 or highervendor/modules.txt, confirmed against go.mod
Vendor present but staleFix it first, then generate
You just have the binaryBuild information embedded in the binary, then compare

For the last row, the Go reference notes that go version -m reports module versions, using vendor/modules.txt when vendoring is enabled.

Steps before you generate

  1. Check whether a vendor directory exists in the repo root.
  2. Read the go directive in go.mod.
  3. Run a normal build to make sure the manifest and go.mod agree.
  4. Generate the SBOM from the same checkout.
  5. Spot check three modules: the version in the SBOM should match modules.txt.

Also note that vendored code is source you ship. Edits made directly inside vendor are not visible in go.mod. If your team patches vendored files, record that in the SBOM notes so a reader knows the component is modified.

A worked example

Suppose a repository has go.mod with a go directive of 1.21, a vendor directory, and a vendor/modules.txt. A teammate bumps one module in go.mod to fix an advisory but forgets to run go mod vendor. This is a made up scenario to show how the pieces interact.

What happens next, based on the behavior the Go reference describes:

  1. A build runs with the default settings. Because vendor exists and the go directive is 1.14 or higher, the go command acts as if -mod=vendor were used.
  2. The go command compares vendor/modules.txt with go.mod, sees they disagree, and reports an error.
  3. The pipeline fails and the team reruns go mod vendor.
  4. After that, the vendored copies, the manifest and go.mod agree again.

Now imagine an SBOM job that reads solely go.mod and runs before the build. It would have reported the bumped version, even though the vendored code still held the old one. The SBOM and the shipped code would disagree, with nothing to warn you. That is the argument for generating the SBOM after a successful build, from the same checkout.

What each source tells you

SourceWhat it tells youWhat it can miss
go.modDeclared module requirementsWhat was actually vendored
go.sumChecksums of module contentWhich modules are in the build
vendor/modules.txtModules and versions copied into vendorLocal edits made inside vendor
Built binaryModule versions embedded at build timeSource files not compiled in

Each row answers a slightly different question. Declared, verified, copied and compiled are four stages. An honest SBOM says which stage it describes.

Making the check automatic

Add two cheap steps to CI. First, run a build with default flags, so the consistency check fires if go.mod and the manifest drift. Second, compare the module list from your SBOM with the module lines in vendor/modules.txt. A short script that sorts both lists and diffs them is enough. Fail the job on any difference you cannot explain.

Also consider a check for edits inside vendor. If your team patches vendored code, git history for the vendor directory shows it. Record such patches in the SBOM as notes so a reader knows the component is modified.

Policy questions to settle

  • Do we commit vendor, or generate it in CI? Committed vendor gives reproducible builds without network access, at the cost of larger diffs.
  • Who is allowed to run go mod vendor, and in which pull requests?
  • How do we review a diff that touches hundreds of vendored files? A common answer is to review modules.txt and go.mod, and trust the tooling for the rest.
  • When we patch vendored code, do we upstream the change or keep a fork?

Write the answers down once. They save an argument every time an advisory lands and someone asks which version is really in the build.

How this interacts with advisories

When an advisory names a module version, your SBOM lets you search for it. If the SBOM came from go.mod while the build used vendor, a search can say not affected when the vendored copy is vulnerable, or the reverse. Matching the source of truth to the build is what makes the answer trustworthy.

Try it yourself

Click through it at your own pace, or open it full screen.

Open a component and compare the version it reports with what your own manifest says.

Where Safeguard fits

Safeguard can scan dependencies and SBOMs from a repository. The free scan at gold.safeguard.sh/scan lets you test a project and compare its inventory with your own expectations.

Quick check

Does go mod tidy change what is in vendor?

No. The Go reference says go mod tidy and go mod download behave the same under vendoring and still use the module cache. Rerun go mod vendor to refresh the directory.

Are test just dependencies of other modules vendored?

No. Packages imported just by tests of packages outside the main module are not included.

Should an SBOM job run before or after the build?

After a successful build, from the same checkout. A build that passes confirms go.mod and vendor/modules.txt agree.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.