skip to content

Dependency Risk

The controls Go gives you over code you did not write: symbol-level advisories, the checksum database behind every download, and what go mod why tells you before an import lands.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

14

What does the Go checksum database at sum.golang.org guarantee that go.sum alone cannot?

level: middleimportance: must knowfreq 55%

answer

  1. the first line has to come from somewhere
  2. trust on first use is the weakness
  3. an append-only public log, key in the toolchain
  4. checked before a new hash is written
  5. catches a moved tag for everyone

basics

~20 s

go.sum only pins the bytes your machine saw first, so a poisoned first download is pinned faithfully forever. The checksum database is a global append-only log of module hashes that the go command consults before writing a new go.sum line.

solid answer

~50 s

`go.sum` is trust on first use: it records the hash your machine observed the first time it fetched a module version, and thereafter refuses anything different. That is strong against later tampering and useless against tampering that happened before the line was written — a poisoned mirror or an intercepted first download is pinned faithfully. The checksum database named by `GOSUMDB`, `sum.golang.org` by default with its public key built into the toolchain, closes that gap: it is a global, append-only, tamper-evident log of module hashes, and before the go command writes a new `go.sum` line it looks the version up there and rejects the download if the hashes disagree. Because the log is append-only and proofs are checked against tree heads already seen, its operator cannot quietly serve one hash to you and another to everyone else. It also catches an author moving a published tag. What it does not do is judge the code.

code

text · 4 lines
text
$ go env GOPROXY GOSUMDB GOPRIVATE
https://proxy.internal.example/mod,direct
sum.golang.org
*.internal.example

go deeper

for a junior

Know that go.sum holds expected hashes and that a separate public database exists so the very first hash you record is checked against what everyone else sees, not just accepted.

for a middle

Explain trust on first use as the concrete weakness, name GOSUMDB and its default, and describe when in the workflow the lookup happens: before a new go.sum line is written, not on every build.

for a senior

Be ready to reason about what survives the check and what does not — a moved tag is caught, a malicious new release is not — and about keeping verification alive behind an internal mirror instead of disabling it.

for a principal

Own the argument for why the database stays on by default in your build platform, what compensating control replaces it wherever it is off, and how much operational cost mirroring the log is worth.

## The gap go.sum leaves open `go.sum` is a list of expected hashes, one or two lines per module version. When the go command downloads a module it hashes what arrived and compares it against that list; a mismatch aborts the build. This is a strong guarantee with one specific hole: **the first line has to come from somewhere.** If your very first fetch of `example.com/lib v1.4.0` was served by a compromised mirror, or intercepted, or came from an origin that had been taken over, then the hash of the hostile copy is what lands in `go.sum` — and from then on the file diligently guarantees that you keep getting the same hostile copy. That pattern is called trust on first use, and it is why a lockfile of hashes alone is not a supply-chain control. ## What the checksum database is The checksum database is a public, append-only, cryptographically verifiable log of `module version -> hash` records. The go command reaches it through the `GOSUMDB` setting, whose default is `sum.golang.org`; the log's public key ships inside the toolchain, so a candidate does not have to trust the network to authenticate the log's answers. Whenever the go command is about to add a hash for a module version that is not yet in `go.sum` — typically during `go get` or `go mod tidy` — it first asks the database what hash that version has. If the answer disagrees with what was downloaded, the download is rejected with a security error rather than being written into `go.sum`. The log is more than a lookup table, and the difference is the whole point. Its records are stored in a Merkle tree, and the go command verifies inclusion proofs for the records it fetches against signed tree heads, keeping the tree state it has already seen in the module cache so it can check that later views are consistent extensions of earlier ones. That structure means the operator cannot *retract* or *rewrite* a record, and cannot present a different view of history to one victim than to everybody else without the split becoming detectable. The property is tamper-evidence and global consistency, not secrecy. ## The three things it therefore buys you 1. **Your first fetch is no longer self-trusted.** A poisoned proxy would have to poison the global log too, which it cannot do silently. 2. **Re-tagging is caught.** Once `v1.4.0`'s hash is in the log it is there permanently. A maintainer who force-pushes that tag onto different commits produces bytes that disagree with the logged hash, and every team fetching that version afterwards is stopped — including teams who never had the original in their `go.sum`. 3. **Everyone's `go.sum` agrees.** Two independent teams that fetch the same version end up with the same hash, which is what makes a `go.sum` diff in a code review meaningful. ## The limits, which matter as much The database says nothing about whether code is *good*. A malicious author who publishes malicious code at a brand-new version gets that version recorded in the log like any other; verification passes and the attack succeeds. It is not an identity system: nothing binds a module path to a vetted publisher, and it does not sign on the author's behalf. It also only covers modules the go command actually looks up — set `GOSUMDB=off`, or match a module path with `GOPRIVATE` (or the narrower `GONOSUMDB`), and you are silently back to trust on first use for exactly those paths. ## Living without direct internet access A network with no egress does not have to give the check up. A module proxy is allowed to mirror the checksum database's endpoints as well as module archives, so an internal `GOPROXY` can answer the go command's log queries too, and verification keeps working end to end behind the mirror. That matters when standing up a self-hosted or air-gapped build environment: the tempting shortcut is `GOSUMDB=off` in the build image, and it quietly converts every dependency in the organisation back to whatever the mirror felt like serving on the day it was first ingested. If the mirror genuinely cannot proxy the log, the honest fallback is to do first ingestion of every new module in one controlled place where the database *is* reachable, commit the resulting `go.sum` lines, and treat that file as a reviewed artefact rather than a generated one. ## How to say it in an interview One sentence: `go.sum` protects you from a *change* to a dependency, the checksum database protects you from a *bad first impression* of one. Everything else follows from that.

  • What is the effect of setting GOSUMDB=off across an organisation's builds?
    `go.sum` still records and enforces hashes, so later tampering is still caught, but nothing checks the first hash you ever record against an outside source. Every module reverts to trust on first use, and whatever your proxy or mirror served on ingestion day becomes the permanent definition of that version for you. It is the single change that most quietly removes the guarantee while leaving every build green.
  • Can a build in a network with no internet egress still use the checksum database?
    Yes. A module proxy may mirror the checksum database's endpoints alongside module archives, so an internal `GOPROXY` can answer the log queries and verification works behind the mirror. Turning the database off is a choice, not a requirement of isolation. If the mirror genuinely cannot proxy the log, do first ingestion of each new module somewhere the database is reachable and treat the resulting go.sum lines as reviewed.
  • Does the checksum database tell you anything about whether a dependency is safe to use?
    No. It establishes that the bytes you got are the bytes everyone else got for that module version, and that the record cannot be rewritten later. A hostile package published at a fresh version is logged exactly like an honest one and verifies cleanly. Judging the code itself is a separate activity, and the database is deliberately not in that business.

go.sum is like writing down a stranger's signature the first time you meet and rejecting anyone who signs differently later; the checksum database is the public registry that tells you whether the first signature was theirs at all.

saying these in an interview costs you the question

  • Says go.sum and the checksum database are the same mechanism
  • Thinks the database certifies that code is safe or audited
  • Believes it verifies author signatures on releases
  • Claims verification requires trusting the network, not a built-in key
  • Assumes an isolated network must set GOSUMDB=off
open as a page

What can a Go dependency's init functions and package-level variables do to every binary that imports it?

level: middleimportance: must knowfreq 55%

basics

~20 s

Anything. Package-level variable initializers and every init function in an imported package run before main, in every binary that pulls the package in, directly or transitively. You cannot import a package and skip them, so whatever they do is imposed on your program.

open as a page

Why does Go's symbol-level vulnerability analysis report fewer findings than a go.mod manifest scan?

level: middleimportance: must knowfreq 55%

basics

~20 s

Because it matches called symbols, not module versions. Go's analysis builds a static call graph and reports an advisory only when a path in your program reaches one of the vulnerable functions. A manifest scan flags every affected version you require.

open as a page

What does `go mod verify` actually check, and what does a passing result not prove?

level: juniorimportance: should knowfreq 44%

basics

~20 s

go mod verify recomputes hashes of the dependencies in your local module cache and compares them with the hashes recorded at download time. It proves nobody edited the cache since; it does not re-download, contact the checksum database, or find vulnerabilities.

open as a page

Which go command shows why a module you never required is in your build, and what does it print?

level: juniorimportance: should knowfreq 45%

basics

~20 s

go mod why -m <module> prints the shortest chain of package imports leading from your main module to that module. If nothing in your build imports it, the command reports that the main module does not need it.

open as a page

What is Go's vulnerability database at vuln.go.dev, and what does a GO-2024-1234 entry add over a CVE record?

level: juniorimportance: should knowfreq 40%

basics

~20 s

Go's vulnerability database at vuln.go.dev is a curated feed of advisories for Go modules and the standard library. Each GO-YYYY-NNNN entry names the affected module, its packages and the exported symbols inside them, the fixed version, and any CVE alias.

open as a page

Why did a CI step running govulncheck stop failing on known vulnerabilities after it switched to -format sarif, and how do you restore the gate?

level: middleimportance: should knowfreq 28%

basics

~20 s

govulncheck exits 0 with -json, -format sarif or -format openvex regardless of findings; only the default text output exits 3 when vulnerabilities are found. Gate on a text-mode run, or parse the report and fail on findings yourself.

open as a page

Why does a Go advisory against the stdlib module require a toolchain upgrade rather than go get?

level: middleimportance: should knowfreq 45%

basics

~20 s

Because a stdlib advisory is about the Go release that compiled your binary, not about anything your go.mod requires. There is no dependency version to bump; you fix it by building with a patched Go toolchain and redeploying every affected binary.

open as a page

A CI runner fails with a module checksum mismatch on a commit that built yesterday — how do you triage it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Treat it as a security event until proven otherwise. Capture the module version and both hashes, diff the runner's GOPROXY, GOSUMDB, GOPRIVATE and GOFLAGS against a healthy runner, then reproduce with an empty module cache. Never retry it away.

open as a page

A Go library you are reviewing shells out with os/exec — what does that cost your service?

level: seniorimportance: should knowfreq 32%

basics

~20 s

It adds a runtime dependency that go.mod does not record and the compiler cannot check. Your otherwise self-contained Go binary now needs an external program present and on PATH wherever it runs, and a missing or different one fails only at runtime, in production.

open as a page

You own a Go library's go.mod and a PR adds a dependency that needs cgo. How do you decide?

level: principalimportance: should knowfreq 26%

basics

~20 s

Decide on what it forces on the teams importing you, not on how good the code is. Cgo in your graph means every consumer needs a C toolchain and loses CGO_ENABLED=0 static cross-compilation. Accept it only behind a separate module or a build constraint.

open as a page

How do you list every package in a Go build that imports unsafe or os/exec, or uses cgo?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

Use go list -deps ./... to expand the full transitive set of packages the build compiles, with a -f template printing each package's import list, then filter for unsafe and os/exec. Packages whose CgoFiles field is non-empty are the ones using cgo.

open as a page

When should you distrust a Go vulnerability report that says the vulnerable symbol is never called?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Whenever the real call is invisible to a static call graph: dispatch through reflect, symbols loaded with the plugin package, code entered through cgo or assembly, and go:linkname. Not called means no path the analysis could see.

open as a page

Your platform team proposes a broad GOPRIVATE pattern so internal modules resolve — how wide should it be, and who owns that call?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Every GOPRIVATE pattern silently removes checksum-database verification for everything it matches, so treat the list as a reviewed security control, not a build convenience. Keep patterns as narrow as the real private paths, and require review to widen one.

open as a page