rss linkedin linkedin gitlab github twitter mastodon instagram
Software Architecture in Go: Vulnerability Management using govulncheck
Sep 23, 2022

Disclaimer: This post includes Amazon affiliate links. If you click on one of them and you make a purchase I’ll earn a commission. Please notice your final price is not affected at all by using those links.

Welcome to another post part of the series covering Quality Attributes / Non-Functional Requirements, this time I’m talking about Security specifically the just announced Vulnerability Management support by the Go team.

What does Vulnerability Management consist of?

Go’s Vulnerability Management support consists of three clear pillars:

  1. Data Sources, including:
  2. Vulnerability Database, which consolidates all the original data sources, and
  3. Tools and Integrations, including:

Vulnerability Management Pillars

Vulnerability Database and Package Discovery Site

The Vulnerability Database is reviewed and curated by the Go Security Team, it’s integrated with the Package Discovery Site and currently includes some useful features such as:

  • Listing known vulnerability reports by recency,
  • Searching for reports,
  • Dedicated landing pages for reports, for example GO-2022-0603, and
  • My favorite one! Listing known vulnerabilities next to the package, including those in the standard library as well as third party ones:

Vulnerability Management Third Party

Detecting vulnerabilities using govulncheck

govulncheck is a new tool that uses the Vulnerability Database to detect known issues local to your codebase, it’s similar to the tools I covered in Software Architecture in Go: Security - Dependencies, however there are three biggest differentiators:

  1. govulncheck supports scanning already-compiled binaries to search for vulnerabilities,
  2. govulncheck reports vulnerabilities only if the vulnerable code is in use, and
  3. govulncheck is free and officially supported by the Go team.

Let’s dig deeper about this new tool.

Installing govulncheck

The code used for this post is available on Github.

govulncheck requires Go 1.18, to install it run the following:

go install

At the moment of writing this post there are no tagged releases however you should still consider versioning it to a concrete commit instead of using latest just like I mentioned in Learning Go: Versioning Tools.

Detecting vulnerabilities in compiled binaries

This feature requires the binary to evaluate to be compiled with Go 1.18 or greater and is one of those features I don’t recall seeing anywhere else. Let’s look at the following example:

 1package main
 3import (
 4	"fmt"
 5	"net/url"
 8func main() {
 9	fmt.Println(url.JoinPath("", "../x"))  //
10	fmt.Println(url.JoinPath("", "../x")) //

The example above demonstrates the GO-2022-0988 vulnerability introduced in Go 1.19 and later fixed in Go 1.19.1. To compile both versions we can use docker, for Go 1.19 running the following command will compile a local binary called go119:

docker run --rm \
  -v "$PWD":/govulncheck -w /govulncheck \
  -e GOOS=darwin -e GOARCH=amd64 \
  golang:1.19.0-buster go build -o go119 .

And running the following command will create a binary called go1191 compiled using Go 1.19.1:

docker run --rm \
  -v "$PWD":/govulncheck -w /govulncheck \
  -e GOOS=darwin -e GOARCH=amd64 \
  golang:1.19.1-buster go build -o go1191 .

Testing the first binary go119 using govulncheck go119 will report the vulnerability:

Vulnerability Management Standard Library

In contrast when using the second binary go1191 via govulncheck go119 no vulnerabilities will be found:

Vulnerability Management Standard Library

This feature allows to retroactively evaluate old binaries to determine if immediate upgrade is necessary, however keep in mind that a regular Go upgrade and binary recompilation is recommended from time to time.

Detecting vulnerabilities that actually affect you

Typically vulnerability tools scan the source code to determine whether imported packages include vulnerabilities or not, govulncheck takes an extra step by inspecting our code to evaluate if in fact the vulnerable code is called, therefore avoiding false negatives and noisy reports.

Let’s look at two examples, both of them are importing a vulnerable package in their go.mod, reported as GO-2022-0603:

3go 1.19
5require v3.0.0

First example consists of using the vulnerable code:

 7func main() {
 8	var t interface{}
 9	yaml.Unmarshal([]byte("0: [:!00 \xef"), &t)

Executing govulncheck ./... correctly reports the vulnerability:

Vulnerability Management Third Party Affected

Second example imports the vulnerable package but doesn’t call the affected function:

 7func main() {
 8	type T struct {
 9		F int `yaml:"a,omitempty"`
10		B int
11	}
13	yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"

In this case executing govulncheck ./... correctly reports the vulnerability but it indicates we are not using it, this is important because it allows remediation to be prioritized correctly, we definitely need to update the package but there’s no urgent reason to do so, that’s the big difference.

Vulnerability Management Third Party Affected

Tools that use go.mod to determine vulnerabilities won’t make this fine distinction, for example Snyk detects both cases no matter what: using vulnerable code and not using vulnerable code.


This new feature is a very welcome addition to the Go ecosystem, although I don’t think this tool will replace existing ones I do believe it will complement them, I look forward seeing what comes next in future releases.

If you’re looking to sink your teeth into more Software Architecture, including Security, I recommend the following content:

Back to posts