Safeguard
Product•

SCA for Go Modules in Safeguard: go.mod, go.sum, Reachability, Fix PRs

Safeguard SCA (GA) reads go.mod and go.sum, walks the module graph, ranks Go CVEs by reachability, EPSS and KEV, and opens fix PRs once you turn them on.

Safeguard Security Team
39 min read

Safeguard's software composition analysis (SCA, generally available) supports Go modules by reading go.mod and go.sum, resolving the full transitive module graph, and matching every module version against vulnerability advisories enriched with EPSS and CISA KEV. Go reachability analysis is generally available for both static and dynamic analysis, so findings in code your program never calls drop down the queue. When a fixed version exists, Griffin can open a fix pull request, but autonomous remediation ships switched off, with every category set to Manual.

The rest of this post covers what Safeguard's docs say it reads for Go, how the module system shapes what a scanner can know, how to scan from a laptop and from CI, how Go prioritisation works, what a good Go fix pull request contains, and where Safeguard stops.

Key takeaways

  • SCA for Go modules (GA). Safeguard's CLI support matrix lists Go Modules, with go.mod as the manifest and go.sum as the lock file, at full support. The legacy dep tool (Gopkg.toml, Gopkg.lock) is listed with limited support.
  • Deep dependency scanning (GA). The docs say deep scanning resolves go.mod and go.sum and walks the resolved graph up to 100 levels, recording the path from your module to each vulnerable one.
  • Vulnerability management (GA). Findings carry CVE or GHSA identifiers, CVSS, EPSS, KEV status, the affected module and version, and the fixed version where one is known.
  • Reachability analysis (GA). The docs' language table marks Go as GA for static and dynamic reachability and describes it as "module-aware, high-fidelity call paths".
  • Autonomous remediation with Griffin fix pull requests (GA, ships off). The AI Remediate docs list Go modules with full support. Nothing is applied until you enable it; every category starts at Manual.
  • Policies and gates (GA) decide whether a build fails. safeguard scan never fails on findings by itself.
  • License compliance (GA) records licenses per component and writes a license.json from CLI scans.
  • CLI and local runner (GA) let you scan on your own machine and send only results to the platform. SAST (GA) also runs in the default CI mode, but this post covers SCA only.
  • Gold Open Source (GA) is a free, no login lookup for affected and fixed versions of a CVE or package.

The problem: Go dependencies are pinned, not safe

Go gets a lot right about dependency management. The Go team's own write up on supply chain security makes the point that every Go build is "locked": the version of every dependency is fully determined by go.mod, and since Go 1.16 build commands fail rather than silently change go.mod. The go.sum file records a cryptographic hash for every module the build uses, and the public checksum database gives the whole ecosystem an append only, verifiable record of those hashes.

None of that tells you whether the version you pinned has a known vulnerability. Pinning means you get the same code every time. If that code contains a CVE, you get the CVE every time too, until someone changes the requirement. A typical Go service pulls in dozens to hundreds of modules once you count everything below its direct requirements, and the module that carries the vulnerable function is often three or four hops away from anything your team chose on purpose.

This is the gap SCA fills. It reads what your build will use, matches it against vulnerability data, and tells you what to change.

What standards and regulators expect

Several requirement sets turn "know what is in your Go binaries and fix what is vulnerable" from good practice into an obligation.

  • NIST SP 800-218, the Secure Software Development Framework (SSDF) version 1.1. Practice PW.4.1 asks producers to acquire and maintain well-secured software components from third parties. PW.4.4 asks them to verify that acquired open source components comply with your requirements throughout their lifecycle, with the example of regularly checking for publicly known vulnerabilities. RV.1.1 covers gathering vulnerability information, including monitoring vulnerability databases, and RV.2.2 covers planning risk responses for each vulnerability. PS.3.2 asks you to collect, protect, maintain and share provenance data for all components, which is where an SBOM comes in.
  • CISA BOD 26-04, "Prioritizing Security Updates Based on Risk" (10 June 2026). For US federal agencies this directive sets remediation timelines from four factors: whether the asset is publicly exposed, whether the CVE is on CISA's KEV catalog, whether exploitation can be automated, and whether exploitation gives partial or total control. The timelines live in the directive's Table 1. BOD 26-04 explicitly revokes BOD 22-01, the 2021 directive that created the KEV catalog, so treat 22-01 deadlines as history only.
  • The CISA KEV catalog itself. CISA describes the catalog as the authoritative source of vulnerabilities exploited in the wild and tells organisations to use it as an input to prioritisation.
  • FIRST EPSS. EPSS publishes, every day, a probability between 0 and 1 that a published CVE will be exploited in the next 30 days. It is a ranking input, not a severity score.

You do not need to be a federal agency for these to matter. Customer security questionnaires, procurement rules and audits increasingly borrow the same ideas: inventory your components, check them against known vulnerabilities, prioritise exploited ones, and keep evidence.

How Go modules shape what a scanner can see

Before looking at Safeguard's flow, it helps to be precise about what the two Go files contain, because the details explain both what SCA does well and where its limits are.

go.mod: the requirement graph

go.mod declares your module path, the Go language version, and require directives for the modules you depend on. The Go modules reference explains that at go 1.17 or higher, go.mod includes an explicit requirement for every module that provides any package transitively imported by a package or test in the main module. Requirements that no package in your module imports directly carry an // indirect comment and sit in their own block.

That change matters for scanners. Before 1.17, go.mod often listed only direct dependencies plus a few indirect ones that minimal version selection needed. From 1.17 onward, the file is close to a complete list of the modules your build can load, which makes it a much better input for SCA.

module example.com/payments

go 1.22

require (
	github.com/example/router v1.8.2
	golang.org/x/net v0.25.0
)

require (
	golang.org/x/text v0.15.0 // indirect
)

go.mod can also carry replace, exclude and retract directives. A replace swaps the contents of a module version for another version or a local directory, and only applies in the main module. An exclude stops a version from being loaded. A retract lets an author mark their own published versions as ones nobody should depend on.

go.sum: hashes, not a lock file in the npm sense

go.sum is often called Go's lock file, and Safeguard's support matrix lists it in the lock file column. It is worth knowing how it differs from a lock file in other ecosystems. Per the Go modules reference, each line has three fields: a module path, a version (possibly ending in /go.mod), and a hash. A line whose version ends in /go.mod holds the hash of that module's go.mod file only; otherwise the hash covers the module's zip contents.

golang.org/x/net v0.25.0 h1:<base64 SHA-256 of the module zip>
golang.org/x/net v0.25.0/go.mod h1:<base64 SHA-256 of its go.mod>

Two consequences follow:

  1. go.sum can list several versions of the same module. The go command may need the go.mod files of multiple versions to run minimal version selection, so their hashes end up in go.sum even though only one version is built.
  2. go.sum can hold stale entries. The reference notes it may contain hashes for versions that are no longer needed, for example after an upgrade, until go mod tidy removes them.

So go.sum tells you which module contents the build trusts. It does not on its own say which single version of each module the build selects. Minimal version selection, run over the requirement graph in go.mod files, decides that.

Minimal version selection

Go picks versions with minimal version selection (MVS). The reference describes it as a walk over a directed graph where each vertex is a module version and each edge is a minimum required version from a require directive, adjusted by exclude and replace in the main module. For each module, the build uses the highest of the minimum versions anything in the graph asks for.

This is why a Go upgrade can be surprisingly contained. Raising one requirement raises the floor for that module and possibly a few of its own requirements. It does not float every other dependency to its newest release.

Where reachability fits for Go

Go is a good language for reachability analysis. It is statically typed and compiled, and the Go project's own vulnerability tooling, govulncheck, analyses source code or a binary's symbol table to narrow reports to vulnerabilities your code may actually call. The Go vulnerability database at vuln.go.dev publishes its reports in the OSV format with symbol level detail.

Govulncheck's documentation is also candid about the limits that apply to any static analysis of Go: calls made through package reflect are not visible to static analysis, use of unsafe can cause false negatives, and binary mode cannot produce call stacks because compiled Go binaries do not carry detailed call information. Keep those limits in mind when you read any reachability verdict, Safeguard's included.

What Safeguard reads for Go

Safeguard's docs describe Go support in four places. Taken together they are consistent.

Docs pageWhat it says about Go
Software Composition AnalysisGo modules is in the supported ecosystems list; manifests and lockfiles are both honoured "so resolution matches what actually installs"
CLI support matrixGo Modules: manifest go.mod, lock file go.sum, full support. Dep (legacy): Gopkg.toml, Gopkg.lock, limited support
Deep Dependency Scanning"Go modules: resolves go.mod and go.sum"
CI/CD Integrationsafeguard ci scan --local reads dependencies from lockfiles and manifests, with go.sum named in the list

Two more entries in the support matrix are relevant to Go teams. It lists Linux ELF executables, Windows .exe files and macOS applications as supported binary file types, and it lists the major container registries. Go services usually ship as a static binary inside a container image, so those paths matter. The docs do not describe what Safeguard extracts from a Go binary specifically (for example, the module list that Go embeds and that the standard library's runtime/debug package exposes as build information), so test binary and image scans on your own artifacts before you rely on them as the only source of truth.

What the docs do not mention for Go is just as important. They say nothing specific about go.work workspace files, the vendor directory and vendor/modules.txt, private module settings such as GOPRIVATE, or how replace directives pointing at local directories are treated. Treat each of those as untested until you have scanned a repository that uses them and checked the result.

How Safeguard handles Go SCA, step by step

There are three ways to get a Go repository into Safeguard: connect it in the web app, scan it with the CLI on your own machine, or scan it in CI. All three produce the same kind of findings in the same findings model. Pick by where your code is allowed to go. The repository connection post covers that choice in depth.

Before you start: tidy the module

This step is about Go, not Safeguard, and it makes every scanner more accurate.

  1. Run go mod tidy and commit the result. This adds missing requirements and hashes and removes stale go.sum entries, so the files the scanner reads match what you build.
  2. Run go mod verify to confirm the module cache matches the recorded hashes.
  3. Run go list -m all and keep the output. It prints the build list that MVS selected. You will use it later to sanity check the SBOM Safeguard produces.

Path A: connect the repository in the web app

  1. In Safeguard, open Integrations from the sidebar and stay on the Add Project/Product tab. You can filter the cards by Source Code (SCM).
  2. Click Connect on the GitHub, GitLab, Bitbucket or generic Git card. Public repositories need only a URL; private ones need credentials. The Bitbucket walkthrough in the Generate SBOM docs shows the pattern: a username and access token or app password, then Verify Credentials.
  3. On the review step, pick the repositories, set the Branch/Tag for each, and set the Project Name and Version (the version defaults to 1.0.0 and is auto-detected from git tags in generation options).
  4. Review the generation options. Include Dev Dependencies controls whether development dependencies are included. Go has no separate dev dependency list in go.mod, but test only imports can still pull in modules, so decide deliberately. Deep Analysis enables Griffin's deeper analysis.
  5. Click Connect. The configuration appears under Project Configurations, and the project appears under Projects with its findings counts.
  6. Turn on extra triggers if you want them. Safeguard already rescans every project daily for newly disclosed vulnerabilities. The docs list On Push, Scheduled and On Release as additional triggers.

The docs give rough timings: under a minute for projects with fewer than 100 dependencies, two to five minutes for 100 to 500, and five to fifteen minutes beyond that.

Path B: scan on your own machine with the CLI

The Safeguard CLI (GA) is a single static binary; the docs note there is no runtime to install alongside it. Install it and open the interactive terminal:

curl -fsSL https://cli.safeguard.sh/install | bash
safeguard

Inside the terminal, type /login to authenticate and then /scan . to scan the folder you are in. Sign in uses an OAuth device code flow that opens your browser. A scan from the interactive terminal while signed in uploads the SBOM and findings to the platform; per the docs, your source never leaves the machine.

You can also scan directly from the command line, which writes results as JSON files under ./output by default:

cd ~/src/payments
safeguard scan --dir .

Each scan writes a folder containing vuln.json, license.json and collection.json, among other files. Two flags are worth knowing for Go:

# Resolve transitive dependencies to a chosen depth
safeguard scan --dir . --resolve-transitive --transitive-depth 100

# Run only selected scanners, write results elsewhere
safeguard scan --dir . --scanners <list> --output out/

The usage docs say --resolve-transitive resolves transitive dependencies through deps.dev, a public open source dependency service. That means module names and versions are looked up outside your network. If your module paths are private or sensitive, agree with your security team whether that lookup is acceptable before you add the flag. For most Go modules at go 1.17 or later, go.mod already lists every module the build can load, so the extra lookup matters less than it does in some other ecosystems.

The docs also describe --org-namespace for naming the namespaces of your own private packages. Their example is an npm style scope. Whether a Go module path prefix such as github.com/yourcompany is treated the same way is not stated, so check it on a real scan.

You can scan a Git repository without cloning it yourself, or a container image that holds your Go binary:

safeguard scan --repo https://github.com/org/repo
safeguard scan --image myregistry.azurecr.io/myapp:v1 \
  --registry-username "$REGISTRY_USER" \
  --registry-password "$REGISTRY_PASSWORD"

The CLI relies on open source scanners that the interactive terminal downloads the first time it runs. On a machine that has never opened the terminal, such as a fresh CI container, the docs say to put those tools on the PATH or point TOOLS_DIR at a directory that holds them. Run safeguard doctor if anything looks wrong; it checks environment, sign in, tools and policy without changing anything.

Path C: scan in CI

The CI/CD docs describe one step: safeguard ci scan. Each push becomes a new version of the Safeguard project, named after the branch and commit.

  1. In Safeguard, create a secret key under Settings › Developer with the scopes scans:trigger and scans:read. You can lock it to one project.
  2. Store it in your CI as a secret named SAFEGUARD_API_KEY.
  3. Add the step. For GitHub Actions the docs give this workflow, saved as .github/workflows/safeguard.yml:
name: Safeguard
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - name: Safeguard scan
        env:
          SAFEGUARD_API_KEY: ${{ secrets.SAFEGUARD_API_KEY }}
          SAFEGUARD_INSTALL_DIR: ${{ runner.temp }}/safeguard-bin
        run: |
          set -eu
          curl -fsSL https://cli.safeguard.sh/install | bash
          "$SAFEGUARD_INSTALL_DIR/safeguard" ci scan --wait --fail-on critical
  1. Push. The run shows the scan result and a link to it in Safeguard.

There are two scan modes, and the choice matters for Go teams with private module hosts:

ModeCommandNeedsScans
On Safeguard (default)safeguard ci scanThe repository connected as an integration, or public on a major hosted Git serviceSAST (GA), SBOM, dependency vulnerabilities
On the runnersafeguard ci scan --localA checkout in the job; no integrationSBOM, dependency vulnerabilities

With --local, only results leave the runner. The docs say it reads dependencies from lockfiles and manifests and name go.sum among them. For GitHub Actions, add actions/checkout@v4 before the step and --local to the command.

To fail the build, add --wait and then either --fail-on with a severity (critical, high, medium or low, applied to dependency vulnerabilities) or --fail-on-gate with a gate name, id or all. The docs are explicit that SAST findings are not part of --fail-on yet. Pin the CLI with SAFEGUARD_VERSION so a new release does not change a build under you.

If you would rather scan on the build machine and gate yourself, the docs show this pattern:

safeguard scan --dir . --output out/
jq -e '[.data.matches[].vulnerability.severity | ascii_downcase]
       | index("critical") | not' out/*/vuln.json

safeguard scan exits 0 when the scan ran, whatever it found; 1 when the command failed; and 2 when results were written but the vulnerability scan did not actually run. There is no severity threshold flag on safeguard scan. Treat exit code 2 as a failure in your script, or a broken scan will look like a clean one.

Reading Go findings in Safeguard

Once the project is scanned, open it and go to the Vulnerabilities tab. The SCA docs list what each finding carries:

FieldWhat it tells you for a Go module
Component and versionThe module path and the version the build uses, for example golang.org/x/net v0.25.0
CVE or GHSA identifiersThe advisory ids that name the vulnerability
Severity with EPSS and KEVCVSS based severity plus exploit probability and whether it is known exploited
Transitive pathThe chain from your module to the vulnerable one
Reachability verdictReachable, conditionally reachable, unreachable or unknown, where available
Fixed versionThe safe upgrade target, where one exists

The Vulnerabilities docs list the data sources Safeguard aggregates: NVD, GitHub Security Advisories, OSV, CISA KEV, vendor security bulletins and security researcher disclosures. The overview splits findings into Critical, High, Medium, Low and Exploitable, the last meaning known active exploitation.

The transitive path is the most useful field for Go

In the ESSCM UI you can click a finding to see the full chain that pulls the vulnerable module in. For Go this answers the first question every reviewer asks: did we choose this module, or did something else? If the vulnerable module is marked // indirect in your go.mod, the path shows which direct requirement is responsible. That decides the fix. Sometimes you raise the indirect requirement directly; sometimes you upgrade the direct dependency that needs it.

Filters that work for a Go backlog

The reachability docs list filters for the Vulnerabilities table. These combine well:

  • reachable:true shows only findings with a real call path.
  • kev:true shows only KEV listed CVEs.
  • epss:>0.3 shows only CVEs with an exploit probability above 30 percent.
  • env:production reachable:true kev:true is what the docs call the P0 backlog.

You can also filter by component name, which helps when one shared module (a networking or crypto library, for instance) accounts for many findings across services.

How prioritisation works for Go

Safeguard's prioritisation engine combines four signals into one score from 0 to 100: reachability, exploitability (EPSS and KEV, plus whether a public proof of concept exists), runtime context, and business context. The weights are configurable per tenant under Settings › Prioritization. The docs give the default bands:

SignalsDefault priority
Reachable, KEV listed, production90 to 100 (P0)
Reachable, EPSS above 0.5, production75 to 89 (P1)
Reachable, high CVSS, staging50 to 74 (P2)
Unreachable, any severity0 to 30 (backlog)

Go reachability in detail

The docs list three kinds of reachability analysis:

  • Static reachability builds a call graph from source, lockfile and bytecode and checks whether any path leads to the vulnerable function. For Go, the docs mark static reachability GA.
  • Dynamic reachability records which modules and functions are loaded and invoked in running workloads, through a runtime collector, and flags never executed paths as cold. The docs mark this GA for Go too. It needs that collector running in your environment, so scope it with Safeguard before you plan around it.
  • Configuration reachability parses framework configuration to see whether code is wired in at all. The docs' examples are from other ecosystems; they do not name a Go framework.

Each finding is classified as reachable, conditionally reachable, unreachable or unknown. Unknown covers cases the analyser cannot decide, such as reflection, dynamic dispatch or native code, and the docs say these are marked conservatively as potentially reachable rather than suppressed. For Go, that means code paths that run through reflect, through interface values the analyser cannot resolve, or through cgo should come back as unknown, not as safe.

The docs are also honest that reachability is weakest for data driven vulnerabilities, where the danger is in what input reaches a function rather than whether the function is called, and for paths that only run under configurations the analyser cannot enumerate. For those, lean on KEV, EPSS and runtime observation. For a wider treatment of how the three signals combine, see prioritise vulnerabilities with reachability, EPSS and KEV in Safeguard, and for Go specifically, reachability analysis for Go modules.

How this maps to BOD 26-04

If you work for, or sell to, a US federal agency, BOD 26-04's four factors line up with signals Safeguard already holds. KEV status is a field on every finding. Asset exposure corresponds to the runtime context labels (internet facing, internal, air-gapped) that feed the score. Exploit automation and technical impact are published by CISA through its Vulnrichment program; Safeguard's docs do not say they ingest those two fields, so you still need to bring them into your triage yourself. Use Safeguard's priority as an input to your BOD 26-04 timeline, not as a replacement for it.

Fixing Go findings

When a fixed version exists, Safeguard's AI Remediate (part of autonomous remediation, GA) works through five stages according to the docs: analysis, solution generation, impact assessment, pull request creation and validation. The docs list Go modules with full support.

The three ways to start a fix

  1. Single vulnerability. Open the finding, click AI Remediate, review the proposed change and click Create Pull Request.
  2. Bulk. Select several findings, click AI Remediate Selected. Griffin batches compatible fixes and you create one pull request per repository.
  3. Page level. On the Vulnerabilities page, click AI Remediate at the top. Griffin analyses all fixable vulnerabilities and shows a plan to approve.

From the CLI, safeguard fix updates vulnerable dependencies; run safeguard fix --help for the flags in your build. The docs say --help is authoritative when it and the docs disagree.

Switched off until you turn it on

Autonomous remediation ships switched off. Every category starts at Manual, and nothing is applied automatically until an administrator enables it. Settings live under Settings › AI Remediate: Auto-Create PRs, Require Approval, Test Integration, Breaking Change Policy and Branch Naming. A sensible start for a Go estate is Require Approval on, Test Integration on, and a breaking change policy that sends anything rated Major or Critical to a human. The autonomous remediation post covers the eight categories and the strategies behind them.

What a good Go fix pull request looks like

The docs say AI Remediate upgrades vulnerable packages to fixed versions, handles transitive dependency conflicts, updates lock files and checks for breaking changes, rating each fix Safe, Minor, Major or Critical. Pull requests include a title with the CVE reference, a description of the vulnerability, a summary of changes, breaking change warnings, test results if configured, and links to advisories.

For Go, review every such pull request against this checklist, whoever opened it:

  • Both files changed. A real Go upgrade changes the require line in go.mod and the matching hashes in go.sum. A diff that touches only one of them will fail go build under the default read only module mode.
  • The indirect block is right. If the fix raises an indirect requirement, the module should stay in the indirect block with its // indirect comment, unless your code now imports it directly.
  • The major version did not change by accident. In Go, a new major version from v2 onward is a different module path (with a /v2 suffix, for example). A fix that changes the import path is a code change, not a version bump, and deserves a closer look.
  • No surprise replace. A fix should not add a replace directive to dodge a version. If one appears, ask why.
  • go mod tidy is clean. Run it on the branch. If it changes anything, the pull request was incomplete.
  • Tests and the binary both build. Run go test ./... and build the release binary. Then rescan the branch and confirm the finding is gone.

Where no fixed version exists, the reachability docs say Griffin can propose a safe downgrade. Treat a downgrade with extra care in Go, because MVS may pull the version back up if another module in the graph requires a higher minimum.

License data for Go modules

License compliance is GA. Safeguard's docs describe license detection from package metadata, SPDX identifiers, full text matching of LICENSE, LICENCE, COPYING and NOTICE files, source file headers, and binary extraction in container layers. When signals disagree, Safeguard raises a license discrepancy finding.

One Go specific point: go.mod has no license field, and the docs' list of metadata sources names other ecosystems' manifests, not go.mod. For Go modules, license identification therefore depends on the other routes, chiefly the license files that ship inside the module. Check a sample of your Go components in the license view and confirm the licenses match what you expect.

From there, the documented features apply as for any ecosystem:

  • Classification into permissive, weak copyleft, strong copyleft, network copyleft, commercial, public domain and unknown.
  • A YAML LicensePolicy with allow, deny and require review lists, scoped by asset labels so a distributed CLI tool and a hosted service can have different rules.
  • Enforcement through guardrails at CI time, at admission and at pull request time.
  • Attribution generation (a NOTICE file, a licenses folder and an SPDX document) for redistributed artifacts. The docs say there is no CLI command for this yet.

A CLI scan writes a license.json next to its other results. For Go teams that ship a static binary to customers, the attribution output matters: every module compiled into the binary is redistributed. See open source license obligations, OpenChain and attribution for the obligations side.

Gates: deciding what blocks a Go build

Policies and gates are GA. Policies evaluate SBOMs and generate findings; gates are the checkpoints that enforce them. You create policies under Settings › Policies with a visual builder or YAML. Conditions include vulnerability severity, CVSS score, EPSS score, license, component age and maintenance status.

Each policy's action decides what a failure does to the gate:

ActionEffect
BlockThe gate fails
WarnThe gate passes, with a warning naming the policy
NotifyThe gate passes and a notification is sent

The CLI does not evaluate policies itself; the docs are explicit that policies and gates run in the platform. To fail a CI job on a gate, use safeguard ci scan --wait --fail-on-gate <gate> with policy outcomes switched on for your workspace. The reachability docs also show policies that use reachability directly, such as blocking production when a reachable or conditionally reachable finding is on KEV. If you want to write policies in plain English rather than YAML, the plain English policies post walks through that flow.

A good first Go policy set is short: block on reachable KEV listed findings in production services, warn on reachable critical findings, notify on everything else. Tighten from there once the backlog is under control.

Decision table: which Safeguard route for which Go situation

Your situationRouteWhat you still own
Repositories on a hosted Git service and you are happy to connect themIntegrations in the web app, plus safeguard ci scan in CIChoosing branches, reviewing fix pull requests
Code must not leave your machinesCLI with /scan . or safeguard ci scan --localKeeping the scanner tools available on fresh CI runners
Private module host, firewalled Git serversafeguard ci scan --local on a runner that can fetch your modulesModule proxy and GOPRIVATE configuration for the build itself
Go service ships as a container imagesafeguard scan --image on the built imageBase image updates, and checking what the binary scan reports
You need federal style prioritisationVulnerabilities tab filters and priority score as inputsExploit automation and technical impact inputs, and BOD 26-04 timelines
A fixed version existsAI Remediate pull request, with Require Approval onMerging, release, deployment
No fixed version existsSafe downgrade proposal, or the fork and patch options belowRisk acceptance with an expiry, compensating controls
You want a quick public check of one moduleGold Open Source lookup, free and without loginActing on the result

How Safeguard compares with running govulncheck alone

Govulncheck is the Go project's own tool, it is free, and every Go team should know it. It is not a competitor to SCA in a platform; the two answer different questions.

QuestiongovulncheckSafeguard SCA (GA)
Does this module or binary call a known vulnerable Go function?Yes, at symbol level, from the Go vulnerability databaseYes, through reachability analysis (GA for Go)
Which of 200 services carry this module version?No, one module at a timeYes, across every project in the tenant
Is the CVE on KEV, and what is its EPSS?Not part of its outputYes, on every finding
Can it fail a build on a policy?You script itGates with Block, Warn and Notify
Does it open a fix pull request?NoYes, once autonomous remediation is enabled
Does it produce an SBOM?NoYes, SPDX and CycloneDX
License data?NoYes

Plenty of teams run govulncheck in developer workflows and Safeguard at the portfolio level. If the two disagree on a finding, read the advisory: the Go database is curated for Go modules with symbol level data, while Safeguard aggregates several sources. Our earlier post on running govulncheck in production covers the tool itself.

What Safeguard does not do

Be clear about the edges before you roll this out.

  • It does not change how Go fetches modules. Safeguard reads go.mod and go.sum and reports on them. It does not replace the module proxy, the checksum database, GOPRIVATE or GOFLAGS settings. Those protect the integrity of what you download; SCA tells you whether what you downloaded has known vulnerabilities. You need both. Our post on Go module security with sumdb, GOPROXY and private modules covers the fetch side.
  • It does not document Go workspaces, vendoring or local replaces. The docs are silent on go.work, vendor/modules.txt and replace directives that point at local paths. Test a repository that uses them before you assume coverage.
  • It does not promise Go standard library or toolchain findings. Vulnerabilities in the Go standard library depend on the toolchain version you build with, which go.mod expresses through the go and toolchain lines. Safeguard's docs do not say whether those are matched. Keep your toolchain current and track Go security releases separately until you have confirmed otherwise.
  • It does not decide reachability with certainty. Reflection, unsafe, cgo and data driven vulnerabilities defeat static analysis. Safeguard marks undecidable cases as unknown and does not suppress them, but an unreachable verdict is still a model of your code, not a proof.
  • It does not fail a build from safeguard scan. That command exits 0 whatever it finds. Failing a build needs safeguard ci scan with --fail-on or --fail-on-gate, or your own script over the output files.
  • It does not merge or ship fixes. Autonomous remediation ships off, every category Manual. Even when enabled, your team reviews, merges, releases and deploys.
  • It does not include SAST results in the CI severity gate yet. SAST is generally available, and the docs say its findings are not part of --fail-on.
  • It does not query findings by priority from the CLI. The reachability docs say there is no CLI command for that yet. Use the web app, Griffin's natural language search, or the connector in Claude.

Common mistakes

  1. Scanning an untidy module. Stale go.sum entries and missing requirements make any scanner less accurate. Run go mod tidy first.
  2. Reading go.sum as the build list. It can hold several versions of one module and stale ones too. Use go list -m all to see what MVS actually selected, and compare it with the SBOM.
  3. Treating exit code 0 as a pass. safeguard scan returns 0 whatever it finds and 2 when the vulnerability scan did not run. Check for 2 and gate on findings properly.
  4. Suppressing everything marked unreachable. Unreachable findings still drop to the backlog, not to zero. Keep KEV listed unreachable findings on a watch list, and remember that unknown means "not proven safe".
  5. Accepting a fix that changes the import path. A move to a new major version changes the module path. That is a code change, and it needs review like one.
  6. Forgetting fork pull requests in CI. GitHub gives fork pull requests no secrets, so the scan step fails there. The docs suggest scanning pushes only, or using pull_request_target with great care, never running the fork's code in that job.
  7. Using deps.dev resolution on private modules without asking. The --resolve-transitive flag looks dependencies up on a public service. Agree that with your security team first.
  8. Scanning only source when you ship images. A Go service in a container also carries the base image. Scan the image you deploy, not only the repository.
  9. Turning on Auto-Create PRs on day one. Start with Require Approval and Test Integration on, and watch the success rate before you widen anything.

FAQ

Does Safeguard support go.mod and go.sum?

Yes. Safeguard's CLI support matrix lists Go Modules with go.mod as the manifest and go.sum as the lock file at full support, and the deep dependency scanning docs say Safeguard resolves both. SCA is generally available.

Does Safeguard support the old dep tool?

Partly. The support matrix lists dep (Gopkg.toml and Gopkg.lock) as limited support. If you still have dep projects, migrating them to modules will give better results in Safeguard and in the Go toolchain itself.

Is reachability analysis available for Go in Safeguard?

Yes. The reachability docs list Go as GA for both static and dynamic reachability. Dynamic reachability needs Safeguard's runtime collector in your workloads, so confirm that part in a scoped demo before you depend on it.

How deep does Safeguard go into Go transitive dependencies?

The deep dependency scanning docs say Safeguard walks the resolved graph up to 100 levels and records the full path from your module to each vulnerable one. From the CLI, --resolve-transitive --transitive-depth 100 sets the depth for a local scan.

Can Safeguard open pull requests to fix vulnerable Go modules?

Yes, through AI Remediate, part of autonomous remediation (GA). The docs list Go modules with full support. It ships switched off with every category set to Manual, so a person starts each fix or approves the pull request until you change that.

Does Safeguard fail my Go build on a critical CVE?

Only if you ask it to. Add safeguard ci scan --wait --fail-on critical to your pipeline, or use --fail-on-gate with a policy gate. The plain safeguard scan command never fails on findings.

Does my Go source code leave my machine when I scan with the CLI?

The docs say a signed in scan from the interactive terminal uploads the SBOM and findings, and that your source never leaves the machine. In CI, safeguard ci scan --local sends only results. The default CI mode is different: Safeguard clones the repository itself.

Should I still run govulncheck?

Many teams do. Govulncheck is the Go project's own tool and works at symbol level against the Go vulnerability database. Safeguard adds a portfolio view, EPSS and KEV, gates, license data and fix pull requests. Running both is reasonable, and disagreements are worth reading.

Does Safeguard find vulnerabilities in the Go standard library?

The docs do not say. Standard library vulnerabilities depend on the Go toolchain you build with. Until you have confirmed Safeguard's behaviour on your own projects, track Go security releases and keep your toolchain current separately.

Where do I check a single Go module for free?

Gold Open Source at gold.safeguard.sh is free and needs no login. Search a CVE or a module to see affected and fixed versions. See using Gold Open Source in your workflow.

To try any of this, download the CLI at https://safeguard.sh/download and read the docs at https://docs.safeguard.sh/docs .

Most vulnerable Go modules get fixed the ordinary way: a maintainer tags a new version and you raise your requirement. When a fix is late, or never comes, the options below are what is left, and they apply to Go modules and to the container images your Go binaries ship in.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

The first demo is a real Safeguard CLI session. It installs the CLI, opens the interactive terminal and asks it to fix a lodash CVE in a JavaScript repository; the assistant reads the finding and advisory, asks before any command that changes something, edits the manifest, runs the install and tests, prints a summary and pushes nothing, then shows /permissions, /model and /scan .. The repository in the demo is JavaScript, not Go. The same flow applies to a Go module: the fix would change go.mod and go.sum, and the commands would be go get, go mod tidy and go test ./....

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

The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. This is the route for Go teams whose code or private modules cannot leave their own machines: the scan runs where the modules already are, and only results go to the platform.

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

The third demo connects Claude to Safeguard: you add Safeguard from Claude's connector directory, where it has a community listing, sign in with OAuth, and choose which tools Claude can use. Once connected, you can ask Claude which of your projects carry a given Go module version or which Go findings are reachable, within the tools your administrator has enabled.

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

Quick check

Why is go.sum not, on its own, a list of the module versions your Go build uses?

Because go.sum can hold hashes for several versions of the same module, which the go command needs to run minimal version selection, and stale hashes left over after an upgrade until go mod tidy removes them. Minimal version selection over the go.mod requirement graph decides which single version is built; go list -m all shows the result.

A Go finding in Safeguard is marked unknown for reachability. Should you suppress it?

No. Safeguard's docs say unknown means the analyser could not decide, for example because of reflection or dynamic dispatch, and such findings are treated as potentially reachable rather than suppressed. Triage them with KEV, EPSS and runtime context instead.

Sources

Last reviewed: 25 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.

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.