How to Use govulncheck for Vulnerability Scanning

Run `govulncheck` directly on your Go source code or binary to identify known vulnerabilities without needing to build the project first.

The midnight dependency panic

You merge a feature branch. Tests pass. The build succeeds. Three days later, a security researcher publishes a critical vulnerability in a transitive dependency your project uses. Suddenly, your production deployment is flagged, your team scrambles to patch, and you spend the weekend writing incident reports. This happens to every Go team eventually. The difference between a smooth patch and a production fire is whether you catch the vulnerability before it ships.

govulncheck exists to stop that cycle. It scans your Go source code or compiled binaries against the official Go Security Advisories database. It does not guess. It does not flag suspicious patterns. It matches your exact dependency graph against a curated list of known, publicly disclosed vulnerabilities.

How it actually works

Think of govulncheck as a customs scanner for your dependency tree. Instead of opening every package and manually inspecting the code for bad practices, it checks the manifest against a known contraband list. When you run it, the tool reads your go.mod file, reconstructs the full dependency graph, and traces every function call that reaches a vulnerable package. If your code never calls the dangerous function, govulncheck marks it as irrelevant. This call-graph analysis is what separates it from simple version checkers that just compare semver strings.

The tool downloads the Go vulnerability database on first run and caches it locally. Subsequent scans run entirely offline unless you force a refresh. It parses your source files, builds a static call graph, and cross-references every reachable function against the advisory database. The result is a precise report that tells you exactly which packages are vulnerable, which functions are actually called, and whether the issue affects your specific code path.

Go's security database uses the GSA identifier format rather than the traditional CVE numbering system. The Go team curates these advisories to ensure they are accurate, actionable, and specific to the Go ecosystem. You get patched version ranges, mitigation steps, and direct links to the original disclosure. The database updates regularly, but the local cache ensures your scans remain fast and deterministic.

Trust the cache. Refresh only when you suspect stale data.

Running your first scan

Here is the simplest way to check your current module:

# Scans every package in the current module directory
# Uses the local cache if available, fetches fresh data otherwise
# Expands ./... to all packages under the current working directory
govulncheck ./...

The ./... pattern is a Go convention that expands to every package under the current directory. It tells the tool to analyze your entire module, including internal packages, command binaries, and test files. The command reads go.mod, resolves all direct and transitive dependencies, and checks them against the database. If everything is clean, the tool prints a single line confirming zero vulnerabilities and exits with code zero.

You can also point it at a specific package when you want to isolate a scan:

# Targets only the main application binary package
# Ignores internal utilities and test helpers outside this path
# Speeds up feedback loops during active development
govulncheck ./cmd/myapp

This approach speeds up development when you are actively modifying a single component and want to verify it before running a full module scan. The tool respects your module boundaries and will not accidentally scan unrelated projects in your workspace.

Keep your module boundaries clean. Scan only what you own.

Scanning compiled binaries

Source code is not always available. You might be maintaining a legacy service, auditing a third-party binary, or working in an environment where only compiled artifacts are shared. govulncheck handles this by reading the build information embedded in the binary.

# Passes a compiled executable directly to the scanner
# Extracts module paths and dependency versions from build metadata
# Reconstructs the dependency graph without requiring source files
govulncheck ./my-app-binary

The Go compiler embeds module version information and build settings into every binary. govulncheck reads that metadata, reconstructs the dependency graph, and runs the same database lookup. The output matches the source-based scan, giving you identical vulnerability reports without needing the original source tree. This makes it useful for post-deployment audits and container image verification.

Binary scanning relies on the build metadata remaining intact. If you strip debug information or use aggressive linker flags that remove module paths, the tool cannot reconstruct the graph. You will see a message like cannot find module information in binary if the metadata is missing. Keep build metadata in production binaries unless you have a strict compliance reason to strip it.

Integrating into continuous integration

Manual scans fall out of habit. Automating the check ensures every pull request gets verified before it reaches the main branch. The tool is designed for pipeline integration by default.

# Runs the scan and fails the pipeline if vulnerabilities are found
# The shell exits immediately with a non-zero status on detection
# Blocks the merge until the dependency is updated or the issue is triaged
govulncheck ./... || exit 1

The non-zero exit code is intentional. CI systems treat any non-zero exit as a failure, which blocks the merge. You can switch to JSON output when you need to pipe results into a reporting dashboard or a custom alerting system:

# Outputs structured JSON for downstream parsing
# Preserves exit code behavior while enabling machine-readable reports
# Writes to stdout so pipeline tools can capture the payload
govulncheck -json ./... > vuln-report.json

The JSON format includes the vulnerability ID, affected package, version range, call stack, and a direct link to the advisory. Pipeline tools can parse this structure to generate Slack notifications, Jira tickets, or compliance reports. The convention in Go tooling is to keep CLI output human-readable by default and reserve flags like -json for automation. Most CI runners cache the $GOCACHE directory between jobs, which makes subsequent scans nearly instantaneous.

Automate early. Block merges on untriaged findings.

Understanding the output and call graphs

When govulncheck finds a match, it prints a structured report. Each entry shows the package path, the vulnerable version range, the advisory ID, and the exact call chain that reaches the dangerous function. The advisory ID follows the GSA-xxxx format, which is the official Go Security Advisory identifier. You can click the link to read the full disclosure, which includes mitigation steps and patched versions.

The tool distinguishes between reachable and unreachable vulnerabilities. If a dependency contains a vulnerable function but your code never calls it, the report marks it as not reached. This prevents alert fatigue. You only fix what actually affects your application. The call-graph analysis relies on static analysis, which means it tracks function pointers and interface calls conservatively. If the compiler cannot prove a function is unreachable, govulncheck assumes it is reachable and reports it.

Static analysis works by tracing every possible execution path through your code. It follows direct calls, method receivers, and interface implementations. When it encounters reflection or dynamic dispatch, it widens the analysis to include all possible targets. This conservative approach guarantees that real vulnerabilities are never missed, but it occasionally flags functions that are theoretically reachable but practically unused. You can verify these edge cases by reading the call stack in the report.

Read the call stack. Verify reachability before upgrading.

Common pitfalls and limitations

govulncheck is precise, but it is not a silver bullet. It only checks for vulnerabilities that have been officially reported and added to the Go Security Advisory database. Zero-day exploits, unpatched issues, and vendor-specific flaws will not appear in the results. The tool also cannot detect runtime configuration errors, missing authentication checks, or insecure deployment practices.

Network timeouts can interrupt the initial database fetch. If the tool cannot reach the advisory server, it falls back to the local cache. If the cache is missing or expired, the command fails with a network error. You can force a refresh by clearing the cache directory or using the -refresh flag in newer versions. The cache lives in your Go environment cache, typically under $GOCACHE or ~/.cache/go-build. Corporate firewalls sometimes block the advisory endpoint. In those cases, you can host a mirror or configure the tool to use a proxy.

False positives do happen when the static call graph cannot prove a function is unreachable. Interface dispatch and reflection sometimes break the analysis chain. When this occurs, the report includes a note explaining why the tool assumed reachability. You can verify the finding by tracing the code manually or by upgrading the dependency to a patched version. The worst vulnerability bug is the one that never logs, but the worst security tool is the one that screams about phantom threats.

Treat warnings as hypotheses. Verify before you panic.

When to use govulncheck versus other tools

Go has several security-related tools. Each serves a different purpose in the development lifecycle.

Use govulncheck when you need to verify that your dependencies do not contain known, publicly disclosed vulnerabilities. Use go vet when you want to catch logical errors, formatting mistakes, and common Go anti-patterns during development. Use gosec when you need to scan for insecure coding patterns like hardcoded credentials, unsafe SQL construction, or missing input validation. Use manual security audits when you are building a high-assurance system that handles financial data, medical records, or cryptographic keys. Use runtime monitoring and intrusion detection when you need to catch active exploitation attempts in production.

Pick the right tool for the right layer. Layered defense beats single-point reliance.

Where to go next