Go modules, Snyk’s scanning for them, and how to find those pesky vulnerable dependencies.
The most surprising thing about Go modules is that they aren’t just about dependency management; they’re a fundamental shift in how Go itself manages code, including versioning and isolation.
Let’s see Snyk in action. Imagine you have a Go project with a go.mod file like this:
module example.com/myproject
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/labstack/echo/v4 v4.10.3
golang.org/x/crypto v0.1.0 // This might have a vulnerability
)
And a go.sum file that looks something like this:
golang.org/x/crypto v0.1.0 h1:oH0Fj3k6c1/5F52BwX4b5k9p+8u5Q9N3C8v7R3Z3/z0=
golang.org/x/crypto v0.1.0/go.mod h1:z8y2E/r9V3L1U8Q5l3u2E7v6H8X7T2K9R5B2J0C9I4=
github.com/gin-gonic/gin v1.9.1 h1:aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789aBcDeF=
github.com/gin-gonic/gin v1.9.1/go.mod h1:1234567890abcdef1234567890abcdef1234567890=
To scan this, you’d typically run:
snyk test --file=go.mod
Snyk then analyzes go.mod and go.sum to understand your project’s direct and transitive dependencies. It queries its vulnerability database for any known CVEs affecting the specific versions you’re using. If it finds a match, it’ll report it.
The output might look like this:
Testing /path/to/your/project...
✗ High severity vulnerability found in golang.org/x/crypto
Description: A denial-of-service vulnerability in the crypto/tls package could allow an attacker to cause a denial-of-service by sending a malicious record.
Info: https://snyk.io/vuln/SNYK-GOLANG-GOLANGXCRYPTO-1234567
Introduced through: golang.org/x/crypto@v0.1.0
From: golang.org/x/crypto@v0.1.0
Remediation:
Upgrade golang.org/x/crypto to version 0.2.0 or later.
Organization: your-org
Package manager: golang
Project name: myproject
Tested 33 dependencies.
This tells you that golang.org/x/crypto at version v0.1.0 has a known vulnerability. The From field shows the direct dependency that pulled in the vulnerable package. The Remediation suggests upgrading to a newer, fixed version.
The problem Snyk solves here is that as your project grows, keeping track of all your dependencies, and their dependencies (transitive dependencies), and whether any of them have known security flaws, becomes an impossible manual task. Go modules, with their go.mod and go.sum files, provide a structured way to declare and lock down these dependencies, making them scannable.
Internally, Snyk parses your go.mod file to build a dependency tree. It looks at the require directives to identify direct dependencies and their exact versions. Then, it uses the go.sum file to verify the integrity and exact versions of all transitive dependencies. This complete picture is crucial because a vulnerability might exist deep within your dependency tree, not in a package you directly imported. Snyk compares this tree against its continuously updated vulnerability database, which contains information on known CVEs, their affected versions, and recommended fixes.
The key levers you control are the versions specified in your go.mod file. By updating a direct dependency, you might also update its transitive dependencies, potentially resolving vulnerabilities. However, Go’s module system is designed to be deterministic; go.sum locks down all resolved versions. If you need to upgrade a vulnerable transitive dependency that’s not directly required, you often need to find a direct dependency that does require a newer version of the vulnerable package, or explicitly add a direct dependency on the newer version yourself if the module author allows it.
When Snyk reports a vulnerability, it doesn’t just tell you that a dependency is vulnerable; it shows you the "path" from your project to that vulnerable dependency. This is invaluable because it helps you understand why that vulnerable version is present. Often, a direct dependency you’ve included is the one mandating the use of an older, vulnerable package.
The next step after fixing vulnerabilities is often understanding the licensing implications of your dependencies.