To fix a vulnerable Go module with the Safeguard CLI, scan the repository, find which module version is affected and why it is in your build, upgrade it with go get, run go mod tidy and your tests, then scan again. The CLI's assistant can do each step on your machine, asking before every command.
Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. If you would rather stay in the chat app you already use, connect Claude or ChatGPT to the Safeguard MCP server and ask about your findings from there.
This guide explains how Go picks module versions, walks through a full fix with real commands, covers the awkward cases, and ends with troubleshooting and an FAQ.
What you will have at the end
Work through this guide once and you will have:
- The Safeguard CLI installed, signed in and running in your Go repository.
- A scan of the module, with results written to
./outputon your machine. - A clear answer for each finding: which module, which version, how it got into your build, and which version fixes it.
- An updated
go.modandgo.sum, tidied, with tests passing. - A second scan that confirms the finding is gone.
- A reviewed diff and a commit message, ready for a pull request that you push yourself.
- A short CI step so the same check runs on every branch.
You need Go installed, a Go module (a folder with a go.mod file) under git, and a Safeguard account. If you have not installed the CLI yet, the platform guides for macOS and Linux cover it in detail.
How Go modules choose versions, and why it shapes the fix
Go dependency management differs from npm or pip, and most failed fixes come from forgetting that. Five facts from the Go docs matter.
Minimal version selection. Go builds a list of module versions by walking the requirement graph from your module and keeping "the highest required version of each module". The Go reference describes the result as "the minimum versions that satisfy all requirements", and notes that the build list "doesn't change when new versions of dependencies are released." In practice this means nothing upgrades by itself. If a vulnerable version is in your build, it stays there until a go.mod somewhere asks for a higher one. Usually that go.mod is yours.
Indirect requirements are listed in your go.mod. For modules at go 1.17 or later, the go command adds an indirect requirement for every module that provides a package your code imports, even indirectly. Those lines carry an // indirect comment. So when a vulnerable module sits three levels deep, you can still raise its version directly in your own go.mod, and minimal version selection will use your higher requirement.
go.sum is the integrity record. The Go docs describe go.sum as a file of checksums that Go uses "to verify the integrity of downloaded module files". Commit it with go.mod. A fix that changes go.mod without the matching go.sum lines will fail for the next person who builds.
Major versions change the import path. From v2 onwards, the major version is part of the module path, for example example.com/mymodule/v2. The Go docs call a new major version "a new module with a separate history". If the only fixed release of a module is a new major version, the fix is a code change, not a one-line bump.
The go line and the toolchain. The go directive sets the minimum Go version, and the toolchain directive suggests a toolchain to use. Findings in the standard library are fixed by moving to a patched Go release, which you do with go get go@VERSION or go get toolchain@VERSION, covered later in this guide.
So almost every Go fix has the same shape: raise one requirement in your go.mod, tidy, test and rescan.
Set up the Safeguard CLI in your Go repository
If you already have the CLI, skip to the walkthrough. Otherwise, these steps take a few minutes.
- Install the binary. On macOS and Linux:
curl -fsSL https://cli.safeguard.sh/install | bash
On Windows, in PowerShell:
irm https://cli.safeguard.sh/install.ps1 | iex
It is a single static binary, so there is no Node, Python or JVM to install alongside it. Builds exist for macOS, Linux and Windows on x86_64 or arm64 (Windows is x86_64 only). Each release ships a SHA256SUMS file if you want to verify the download first.
- Check the install:
safeguard --version
safeguard doctor
safeguard doctor checks the environment, sign in, profile, policy and tools, and prints what is wrong in the order that matters. It changes nothing.
- Move into your module and open the interactive terminal:
cd ~/code/orders-service
git status
safeguard
Start in the module root, the folder that holds go.mod. The @ file picker and /resume both work from the folder you started in.
-
Type
/login. Your browser opens so you can sign in. -
Type
/permissionsand pick a mode. For a dependency fix, Ask or Auto are both reasonable. Ask lets reads run and asks before every edit and command. Auto, the default, runs read only commands and whatever your policy allows, and asks for anything else. -
Optionally type
/modelto see or switch the AI model for the session.
Step by step: find, fix and verify a vulnerable Go module
This walkthrough uses a sample service called orders-service with a hypothetical dependency, example.com/acme/parser. Swap in your own module paths.
1. Commit or stash your work
git status
git switch -c fix/go-deps
A clean tree keeps the fix alone in the diff and easy to discard.
2. Scan the module
Inside the interactive terminal, type:
/scan .
Or, from a normal shell:
safeguard scan --dir .
safeguard scan builds an SBOM of the folder and matches it against vulnerability databases. Results are JSON files under ./output, including vuln.json and license.json. Your source code stays on your machine; only the SBOM and findings go to the platform.
If you want a quick look with nothing uploaded at all, run the dependency scanner locally:
safeguard findings sca --dir .
safeguard findings prints one normalized list with the package and version, the fixed version where one is known, a description and remediation. It registers no project and uploads no file.
3. Ask what matters
In the interactive terminal, ask in plain language:
Summarise the Go findings in output/vuln.json. For each one, give the module,
the version we use, the first fixed version, and whether it is a direct or
indirect requirement in go.mod.
You can attach files with @, for example @go.mod, so the assistant reads exactly what you mean. It reads files without changing them, so this step needs no approvals in Ask or Auto mode.
4. Find out why the module is in your build
Before you change a version, know who asked for it. Go has two commands for this.
go mod why -m example.com/acme/parser
go mod graph | grep 'example.com/acme/parser'
The Go reference says go mod why "shows a shortest path in the import graph from the main module" to the package, and with -m it treats its arguments as modules. go mod graph prints the requirement graph one edge per line, so grep shows every module that requires the vulnerable one. If go mod why reports that the main module does not need the package, the requirement may be stale and go mod tidy may remove it.
You can ask the assistant to run these for you. They are read only, so in Auto mode they run without a prompt.
5. Check which versions exist
go list -m -u example.com/acme/parser
go list -m -versions example.com/acme/parser
The first shows the version you use and the latest available. The second lists tagged versions. Retracted versions are left out of that list unless you add -retracted, which is what you want: a retracted version is one its authors said "should not be depended upon".
Pick the lowest version that fixes the finding, within the same major version if one exists. That keeps the change small. A patch release, per the Go docs, is for changes "that don't affect the module's public API".
6. Ask the assistant to make the fix
Now hand it over:
Upgrade example.com/acme/parser to the first fixed version from the scan.
Use go get, then go mod tidy, then go build ./... and go test ./...
Do not change anything else. Show me the go.mod diff before committing.
The assistant proposes each command and waits. You will see something like go get example.com/acme/parser@v1.4.6 and a prompt to approve it. Read it, then press y or n. After approval Go prints a line in the documented form:
go: upgraded example.com/acme/parser v1.4.2 => v1.4.6
It asks again before go mod tidy, the build and the tests. If a test fails, it reads the output and suggests a change, which also waits for your approval.
Here is the same flow in a recorded session. It uses an npm sample project rather than Go, but every step matches: the assistant reads the finding, asks before each command, edits the manifest, runs the install and tests with approval, and pushes nothing.
Click through it at your own pace, or open it full screen.
7. Doing it by hand instead
You do not have to use the assistant. The manual version is four commands:
go get example.com/acme/parser@v1.4.6
go mod tidy
go build ./...
go test ./...
go mod tidy adds modules that are needed but missing and removes ones that no longer provide any package you import. Run it after every version change so go.mod and go.sum describe what you actually build.
8. Rescan and confirm
safeguard scan --dir .
Or /scan . in the terminal. Then ask: "Compare this scan with the previous one. Which findings were fixed, and is anything new?" An upgrade can pull in newer versions of other modules, so check the whole list, not just the one you fixed.
9. Review the change
git diff go.mod go.sum
safeguard review
safeguard review reviews the working tree changes for correctness and security. Add --base main to diff against a branch instead. For a pure dependency bump, expect the diff to touch go.mod and go.sum and nothing else. If you see source changes you did not ask for, stop and read them. Our guide on reviewing AI generated code before merge goes deeper on this.
10. Commit and open the pull request yourself
Ask the assistant for a commit message, then commit and push with your own git commands. In the recorded session above, the assistant prints a summary and a suggested message and pushes nothing. Keep it that way, so a person always decides what leaves the machine.
Harder cases you will meet
The vulnerable module is only an indirect requirement
Add a direct requirement at the fixed version:
go get example.com/acme/yaml@v2.4.1
go mod tidy
Your go.mod now requires the higher version, with an // indirect comment if your code does not import it directly. Minimal version selection uses the highest required version, so the module that pulled in the old version now gets the fixed one. Run the tests, because that module was built against an older release.
When the parent module has its own release that requires the fixed version, upgrading the parent is cleaner. Use go mod graph to find the parent, go list -m -u to see if it has a newer release, and upgrade that instead.
The only fix is a new major version
Go treats example.com/acme/parser/v2 as a different module. Upgrading means changing import paths in your code:
go get example.com/acme/parser/v2@latest
grep -rl 'example.com/acme/parser"' --include='*.go' .
Then update each import, fix compile errors, run go mod tidy and test. This mechanical, many file job suits the assistant: ask it to list the files first, then edit them with your approval.
No fixed version exists yet
You have three options, all documented in the Go reference:
- Stop using the affected code. The Go tutorial suggests this when no fix exists: stop calling the vulnerable function and implement the behaviour another way.
- Use a patched fork with replace. A
replacedirective "replaces the contents of a specific version of a module" with contents found elsewhere. It only applies in your main module'sgo.mod. The Go docs note that with replace, Go tools do not authenticate the external module in the usual way, so review the fork. - Exclude a specific bad version. An
excludedirective "prevents a module version from being loaded", which can push Go to a higher version. It also only applies in the main module.
replace example.com/acme/parser v1.4.2 => example.com/ourfork/parser v1.4.2-patched
exclude example.com/acme/parser v1.4.2
Record why each one is there in a comment, and remove it once an upstream fix ships.
The finding is in the Go standard library
Standard library findings are fixed by moving to a patched Go release, not by go get on a module. Raise the minimum or the suggested toolchain:
go get go@1.23.4
go get toolchain@go1.23.4
go version
Use the release that fixes your finding, not these example numbers. The Go toolchain docs explain that with the default GOTOOLCHAIN=auto, the go command downloads and runs a newer toolchain when go.mod asks for one, and that toolchain downloads have their checksums checked by the Go checksum database. Also update the Go version in your Dockerfiles and CI images, or production keeps building with the old compiler.
You vendor dependencies
If your repository has a vendor folder and go 1.14 or higher, Go uses it automatically. After any version change, run:
go mod vendor
The Go reference says the go command reports an error if go.mod has changed since vendor/modules.txt was generated. Commit the updated vendor folder with the go.mod change.
Do the work properly with the Safeguard CLI
The Go commands above are the fix. The Safeguard CLI is how you do the whole job in one place: find the finding, understand it, change the code, prove it worked, and keep your source on your machine.
Install and first session
Install with the one line command from the setup section, run safeguard, type /login, then /scan .. After that, ask in plain language, such as "how many critical vulns do we have".
Writing and fixing code
The assistant has tools for your local files and for your Safeguard data, and it picks whichever the question needs. It can write files, edit code and run commands, and those actions ask before they act. That is why it suits Go fixes so well: the same session can read vuln.json, run go mod why, edit imports for a major version move, run go test ./..., and rescan, all with your approval at each step. Use @ to attach a file and /resume to pick up where you left off in the same folder.
If the assistant edits a file you did not want changed, /rewind takes you back, and safeguard checkpoints restore does the same from the shell (add --dry-run to preview). Checkpoints cover files the assistant edits, not what a shell command writes, so for go.mod changes made by go get, git is your undo.
Using your Claude, ChatGPT or Codex plan
Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. To connect a model, see the CLI docs or type /model.
Approvals
/permissions sets how much runs without a prompt. Ask lets reads run while edits and commands ask first. Accept edits lets edits in this folder and read only commands run, and asks for other commands. Plan only reads and plans. Auto, the default, runs read only commands and what your policy allows, and asks for anything else. Strict runs pre-approved actions only and is meant for CI. A --yolo flag approves everything; keep it for throwaway sandboxes.
For a dependency fix, Auto works well: go list and go mod why run straight away, and go get, go mod tidy and go test wait for your y.
The same session on your desktop and phone
A CLI session is live. It appears in the web app, the desktop app and the phone app, and those devices can send work back to a computer running the CLI. Start in the terminal, continue on the desktop, check findings on your phone. The desktop app is available for Windows, macOS and Linux, and the phone apps for iOS and Android. This recording shows the desktop app sending work to a computer, asking which folder, and waiting for Approve or Deny on each command and file edit until the upgrade is done and tests pass.
Click through it at your own pace, or open it full screen.
CI with safeguard exec and safeguard ci
To keep the fix fixed, add a check to your pipeline. safeguard ci scan --local scans the checkout on the CI runner and uploads only the results:
- name: Safeguard dependency scan
env:
SAFEGUARD_API_KEY: ${{ secrets.SAFEGUARD_API_KEY }}
run: |
curl -fsSL https://cli.safeguard.sh/install | bash
safeguard ci scan --local --wait --fail-on high
The key comes from Settings, Secret Keys, with the scans:trigger scope, plus scans:read for --wait. For a written summary in the job log, safeguard exec "summarise the critical findings in this repository" runs one non-interactive assistant turn. Its default permission mode is strict, so anything not pre-approved is denied. Policies and gates are evaluated in the platform, not by the CLI. Our safeguard exec in CI guide has full pipeline examples.
Troubleshooting
go get says a module requires a newer Go. Go prints a line saying it is switching toolchains, then writes new go and toolchain lines to go.mod. That is expected with GOTOOLCHAIN=auto. If your environment sets GOTOOLCHAIN=local, install the newer Go yourself, or pick a fixed version of the module that supports your Go version.
go mod tidy puts the old version back. Something else requires it at a higher level than you think, or your requirement was not saved. Run go mod graph | grep MODULE and check which version your go.mod lists. Remember that minimal version selection always picks the highest required version, never a lower one.
The rescan still shows the finding. Check that you rescanned the same folder, and that go.mod really changed (git diff go.mod). If the module is vendored, run go mod vendor. If the finding is in a binary or container image rather than source, rebuild the image and scan it with safeguard scan --image NAME.
Checksum mismatch errors. A go.sum mismatch means the downloaded content differs from the recorded hash. Do not delete go.sum to make the error go away. Run go mod verify to check the module cache, and look at whether a replace or a proxy setting changed.
Common mistakes
- Running
go get -u ./...to fix one finding. The-uflag upgrades every module that provides imported packages to its latest version. That turns one small fix into a large, risky change. Usego get MODULE@VERSION, orgo get -u=patchif you want patch releases only. - Forgetting
go.sum. Commit both files. A missinggo.sumline breaks the next build. - Skipping
go mod tidy. Without it, stale requirements and missing checksums pile up and later scans get noisy. - Ignoring indirect findings. They are in your build. Raise the requirement in your own
go.mod. - Fixing the standard library with a module upgrade. Standard library findings need a patched Go release, and the CI and Docker images must move too.
- Leaving
replaceandexcludelines forever. Add a comment with the reason and remove them when upstream ships a fix. - Approving commands without reading them. The approval prompt is your control. Read every
go getline before you press y.
FAQ
Does Safeguard support Go modules?
Yes. Safeguard reads Go manifests such as go.mod when it builds an SBOM, and safeguard scan --dir . matches the result against vulnerability databases. The results land in ./output on your machine.
Do I still need govulncheck?
You can use both. The Go team's govulncheck reports vulnerabilities based on whether your code calls the affected functions, and the Go tutorial shows go get followed by a rerun as the fix. Safeguard adds a record in the platform, reachability analysis, policies and gates, and an assistant that makes the change with your approval.
Can I use my existing AI subscription?
Yes. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. You can also connect Claude or ChatGPT to the Safeguard MCP server and use Safeguard from the chat you already use.
Will the assistant push to my repository?
Not unless you ask it to and approve the command. In the recorded session it prints a summary and a suggested commit message and pushes nothing. Committing and pushing yourself is the safer habit.
Does my source code leave my machine?
The scan builds the SBOM locally and uploads the SBOM and findings, not your source code. The model you choose sees what the assistant reads to answer you, so start it in the right folder.
What if the fixed version breaks my tests?
Read the failure, then decide: adapt your code, pick a different fixed version, or hold the upgrade and use replace or exclude as a stopgap. The assistant can propose code changes for a failing test, each one with your approval.
How do I undo a change the assistant made?
Use /rewind in the session or safeguard checkpoints restore from the shell for files the assistant edited. For changes made by commands, such as go get rewriting go.mod, use git: git checkout go.mod go.sum.
Try it yourself
This walkthrough shows how to set up the runner on your own machine, pick the platform, start it, approve it with its device code, then use the same CLI to scan from the terminal.
Click through it at your own pace, or open it full screen.
When you are ready, download the Safeguard CLI and read the CLI docs, then run /scan . in your own Go module. For more ways to use the assistant day to day, see the Safeguard CLI as a security aware coding assistant.
Quick check
A vulnerable module is only an indirect requirement. How do you fix it?
Run go get MODULE@FIXEDVERSION in your own module, then go mod tidy. Minimal version selection uses the highest required version, so your requirement wins. Upgrading the parent module is cleaner when it has a release that already requires the fix.
Why is go get -u ./... a poor way to fix one finding?
It upgrades every module that provides imported packages to its latest version, so one fix becomes a large change. Upgrade the single module to its first fixed version instead.
How do you fix a finding in the Go standard library?
Move to a patched Go release with go get go@VERSION or go get toolchain@VERSION, then update the Go version in your CI and container images too.
Sources
- Safeguard CLI documentation
- Safeguard CLI interactive mode
- Safeguard downloads
- Go: Managing dependencies
- Go Modules Reference
- Go: Module version numbering
- Go: Toolchains
- Go Vulnerability Management
- Go tutorial: Find and fix vulnerable dependencies with govulncheck
Last reviewed: 21 October 2026.