Golang - Patching
A practical guide to patching vulnerabilities in Go modules, covering different scenarios and utilizing Go-native tooling like govulncheck.
Vulnerability Management
Paraphrasing a little bit about the concept OWASP provides, vulnerability management is a cyclic process of controlling risk by identifying, prioritizing, fixing and monitoring weaknesses in Software Products.
This article will focus on remediation and mitigation. My motivation to write this article comes from the need to develop a practical example to show some common strategies and ways to tackle different situations during the fixing/remediation stage in this case inside the Go-lang environment.
For this purpose I created a repository with common scenarios to practice patching strategies. First clone this repo, and follow the instructions below. https://github.com/Tato418/go-vuln-lab
Happy hacking :)
The lab
This lab covers Software Composition Analysis (SCA) only — i.e. vulnerabilities in third-party Go modules. It uses Go-native tooling only:
| Tool | Role |
|---|---|
govulncheck |
Call-graph aware vulnerability scanner backed by vuln.go.dev |
go list -m -u all |
Reports modules with available upgrades |
go mod graph / go mod why -m |
Dependency graph + reverse lookup |
go mod verify |
Checksum integrity verification |
go mod tidy |
Module graph normalization via Minimum Version Selection (MVS) |
Scenarios
| Slug | Pattern | Key technique |
|---|---|---|
01-direct-cve-upgrade |
Direct dep with known CVE | go get pkg@fixed + import path migration |
02-transitive-cve-mvs |
Indirect dep vulnerable | Top-level require forces MVS upgrade |
03-callgraph-triage |
Vuln exists but unreachable | Read govulncheck reachability output |
04-replace-with-fork |
Upstream unmaintained | replace directive + local fork |
05-stdlib-toolchain-bump |
Stdlib CVE | Bump go directive in go.mod |
1
2
3
4
5
6
7
8
9
10
11
12
13
go-vuln-lab/
├── README.md
├── Makefile # Lab-wide targets
├── tools/ # Install script + shared shell lib
├── scripts/ # Reusable scan / diff / update scripts
├── docs/ # Methodology, decision matrix, tool reference
└── scenarios/
└── NN-<slug>/
├── vuln/ # Vulnerable module (run govulncheck here)
├── fixed/ # Patched module (run govulncheck here)
├── tests/ # Exploit test, fails on vuln/ passes on fixed/
├── Makefile # scan / test / swap
└── GUIDE.md # Walkthrough
Scenario 01 - Simple pkg upgrade
The first scenario shows a simple upgrade dependency scenario. First enter de vuln folder and execute govulncheck ./...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
govulncheck ./...
=== Symbol Results ===
Vulnerability #1: GO-2022-0956
Excessive resource consumption in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2022-0956
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Example traces found:
#1: main.go:49:26: loadconfig.LoadConfig calls yaml.Unmarshal
Vulnerability #2: GO-2021-0061
Denial of service in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2021-0061
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Example traces found:
#1: main.go:49:26: loadconfig.LoadConfig calls yaml.Unmarshal
Vulnerability #3: GO-2020-0036
Excessive resource consumption in YAML parsing in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2020-0036
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Example traces found:
#1: main.go:49:26: loadconfig.LoadConfig calls yaml.Unmarshal
Your code is affected by 3 vulnerabilities from 1 module.
This scan also found 0 vulnerabilities in packages you import and 8
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
The output shows the following vulnerabilities, in total 3:
1
2
3
4
5
6
7
8
9
10
11
*Vulnerability #1: GO-2022-0956*
*Excessive resource consumption in gopkg.in/yaml.v2*
*Vulnerability #2: GO-2021-0061*
*Denial of service in gopkg.in/yaml.v2*
*Vulnerability #3: GO-2020-0036*
*Excessive resource consumption in YAML parsing in gopkg.in/yaml.v2*
*Vulnerability #3: GO-2020-0036*
*Excessive resource consumption in YAML parsing in gopkg.in/yaml.v2*
So we have 3 vulnerabilities in one package which are fixed in version gopkg.in/[email protected]
Fix by upgrading the dependency
Since the vulnerabilities are present in only one package we can proceed to bump the version to the latest non vulnerable. Also, since the bump is from version 2.2.0 to 2.2.8 we should be able to upgrade without having breaking changes. However, always run the tests.
go get gopkg.in/[email protected]
1
2
3
4
5
6
7
8
9
10
➜ govulncheck ./...
=== Symbol Results ===
**No vulnerabilities found.**
Your code is affected by 0 vulnerabilities.
This scan also found 0 vulnerabilities in packages you import and 8
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
Scenario 02 - Transitive CVE (MVS-driven fix)
The dependency is not ours — we don’t import it directly — but it’s still
in go.mod as an // indirect entry and still ships in your binary.
govulncheck walks the whole call graph including across module
boundaries, so the finding surfaces even though your code only touches
mylib.
The fix is the Minimum Version Selection (MVS) trick: add an explicit
top-level require for the patched version, and MVS will pick it over the
transitive dep’s older pin.
First, execute govulncheck:
1
2
3
4
5
Vulnerability #1: GO-2022-0956
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Example traces found:
#1: main.go:15:30: app.main calls mylib.LoadConfig, which calls yaml.
Detect which pkg imports the library,
1
go mod why -m gopkg.in/yaml.v2
1
2
3
4
# gopkg.in/yaml.v2
example.com/app
github.com/example/mylib <---- Transitive dependency
gopkg.in/yaml.v2
Fix — the MVS override
Edit vuln/go.mod, line 7
1
2
-require gopkg.in/yaml.v2 v2.2.0 // indirect
+require gopkg.in/yaml.v2 v2.4.0 // indirect
1
2
3
4
5
6
7
8
9
module example.com/app
go 1.22
require github.com/example/mylib v0.0.0
require gopkg.in/yaml.v2 v2.2.8 // indirect
replace github.com/example/mylib => ../testdata/mylib
Then execute:
1
go mod tidy # rewrites go.sum with v2.2.8 hashes
You do need to touch testdata/mylib/go.mod — the library still claims v2.2.0, but MVS resolves to the max(v2.2.0, v2.2.8) = v2.2.8, which is the patched line.
1
2
3
4
5
6
7
8
9
10
➜ govulncheck ./...
=== Symbol Results ===
No vulnerabilities found.
Your code is affected by 0 vulnerabilities.
This scan also found 0 vulnerabilities in packages you import and 8
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
Scenario 03 - Callgraph triage
A vulnerable module is in your go.mod, but your code never calls any
of the vulnerable functions. govulncheck does call-graph aware
analysis and reports:
1
2
3
4
5
6
7
8
9
10
➜ govulncheck ./...
=== Symbol Results ===
No vulnerabilities found.
Your code is affected by 0 vulnerabilities.
This scan also found 3 vulnerabilities in packages you import and 11
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
The way of solve this issue is defense in depth, even if exit is 0, upgrade the dependency is key so in a future, code change can’t accidentally introduce a reachable vuln.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
=== Symbol Results ===
No vulnerabilities found.
=== Package Results ===
Vulnerability #1: GO-2022-0956
Excessive resource consumption in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2022-0956
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Vulnerability #2: GO-2021-0061
Denial of service in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2021-0061
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Vulnerability #3: GO-2020-0036
Excessive resource consumption in YAML parsing in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2020-0036
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Fix
1
2
3
4
5
6
module example.com/cfgfmt
go 1.22
require gopkg.in/yaml.v2 v2.2.8
Scenario 04 - Replace with a patched fork
When an upstream module ships a known-vulnerable dep and the maintainer is unresponsive (archived repo, no commits in years, no advisory response), the right move is often to maintain a fork and use the replace directive in your go.mod to point at it.
The replace directive also works against remote forks (e.g. replace github.com/foo/bar => github.com/yourorg/bar v1.2.3); here we use a local directory for reproducibility.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
➜ govulncheck ./...
=== Symbol Results ===
Vulnerability #1: GO-2022-0956
Excessive resource consumption in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2022-0956
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Example traces found:
#1: main.go:15:33: app.main calls upstream.LoadConfig, which calls yaml.Unmarshal
Vulnerability #2: GO-2021-0061
Denial of service in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2021-0061
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Example traces found:
#1: main.go:15:33: app.main calls upstream.LoadConfig, which calls yaml.Unmarshal
Vulnerability #3: GO-2020-0036
Excessive resource consumption in YAML parsing in gopkg.in/yaml.v2
More info: https://pkg.go.dev/vuln/GO-2020-0036
Module: gopkg.in/yaml.v2
Found in: gopkg.in/[email protected]
Fixed in: gopkg.in/[email protected]
Example traces found:
#1: main.go:15:33: app.main calls upstream.LoadConfig, which calls yaml.Unmarshal
How replace works
replace is a per-module directive in go.mod that overrides the
resolved source for a given module path. The replacement target can be:
| Target form | Meaning |
|---|---|
../local/path |
Local directory (this scenario) |
./subdir |
Local subdir |
github.com/yourorg/bar v1.2.3 |
Another module, pinned to a version |
github.com/yourorg/bar commit-sha |
Another module, pinned to a commit (most explicit) |
github.com/yourorg/bar branch |
Another module, pinned to a branch head (avoid in prod) |
Scenario 05 - Patching std library
Third-party deps are not the only attack surface. The Go standard
library is versioned per toolchain, not per Go module. When a
stdlib CVE is published, the fix ships in the next patch release of
the toolchain — and your go.mod has to be updated to pick it up.
go 1.22.0— language features + module graph compatibilitytoolchain go1.22.10— exact toolchain to use (downloaded automatically)
In Go 1.21+, the toolchain directive in go.mod lets you pin
exactly which compiler / stdlib is used, and go will download it
on demand. Older Go versions had only the go directive, which
loosely selected the language level but used the host’s toolchain.
Running govulncheck gives us the following.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
=== Symbol Results ===
Vulnerability #1: GO-2026-4601
Incorrect parsing of IPv6 host literals in net/url
Standard library
Found in: net/[email protected]
Fixed in: net/[email protected]
Example traces found:
#1: main.go:34:30: pkiverify.LoadCert calls x509.ParseCertificate, which eventually calls url.Parse
Vulnerability #2: GO-2025-4011
Parsing DER payload can cause memory exhaustion in encoding/asn1
...
Found in: encoding/[email protected]
Fixed in: encoding/[email protected]
Vulnerability #3: GO-2025-4010 ...
Vulnerability #4: GO-2025-4009 ...
... etc, ~6+ reachable stdlib CVEs
In this case fixing the vulnerability is straightforward, proceed to edit go mod and replace the go version with the patched version, then execute:
1
2
➜ go mod tidy
go: downloading go1.26.4 (linux/amd64)
Execute again to check if the vulnerability is fixed.
1
2
➜ govulncheck ./...
=== Symbol Results ===
Further reading
- vuln.go.dev — official Go vulnerability database
- Go security guide
govulncheckdesign doc
