Dependency Security and Vulnerability Scanning in Go
Introduction
Dependencies are a critical part of modern software development, but they also introduce security risks. This guide teaches you how to manage dependencies securely and scan for vulnerabilities in Go projects.
Core Concepts
Dependency Risks
- Known Vulnerabilities: Published CVEs in dependencies
- Outdated Packages: Missing security patches
- Malicious Packages: Intentionally harmful code
- Supply Chain Attacks: Compromised dependencies
Scanning Tools
- go list: Built-in dependency listing
- govulncheck: Official vulnerability scanner
- nancy: Dependency vulnerability scanner
- snyk: Commercial vulnerability scanning
Good: Dependency Management
Using go.mod for Version Control
// โ
GOOD: go.mod with pinned versions
// go.mod example:
module github.com/myapp/app
go 1.21
require (
github.com/gorilla/mux v1.8.0
github.com/lib/pq v1.10.9
github.com/sirupsen/logrus v1.9.3
)
exclude (
github.com/vulnerable/package v1.0.0
)
replace (
github.com/old/package => github.com/new/package v1.0.0
)
Listing Dependencies
# โ
GOOD: List all dependencies
go list -m all
# โ
GOOD: List with versions
go list -m -u all
# โ
GOOD: List in JSON format
go list -m -json all
Good: Vulnerability Scanning
Using govulncheck
# โ
GOOD: Scan for known vulnerabilities
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
# โ
GOOD: Scan specific package
govulncheck github.com/vulnerable/package
Programmatic Vulnerability Checking
package main
import (
"fmt"
"os/exec"
)
// โ
GOOD: Run vulnerability scan
func ScanVulnerabilities() error {
cmd := exec.Command("govulncheck", "./...")
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("Vulnerabilities found:\n%s\n", string(output))
return err
}
fmt.Println("No vulnerabilities found")
return nil
}
// โ
GOOD: Check specific dependency
func CheckDependency(pkg string) error {
cmd := exec.Command("govulncheck", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("Vulnerabilities in %s:\n%s\n", pkg, string(output))
return err
}
return nil
}
Good: Secure Dependency Updates
Safe Update Process
# โ
GOOD: Update specific dependency
go get -u github.com/package/[email protected]
# โ
GOOD: Update all dependencies
go get -u ./...
# โ
GOOD: Update with security patches only
go get -u=patch ./...
# โ
GOOD: Verify updates
go mod verify
# โ
GOOD: Tidy dependencies
go mod tidy
Automated Dependency Updates
package main
import (
"fmt"
"os/exec"
"time"
)
// โ
GOOD: Scheduled dependency updates
func ScheduledDependencyUpdate() {
ticker := time.NewTicker(7 * 24 * time.Hour) // Weekly
defer ticker.Stop()
for range ticker.C {
if err := UpdateDependencies(); err != nil {
fmt.Printf("Update failed: %v\n", err)
}
}
}
func UpdateDependencies() error {
// Get updates
cmd := exec.Command("go", "get", "-u", "./...")
if err := cmd.Run(); err != nil {
return err
}
// Verify
cmd = exec.Command("go", "mod", "verify")
if err := cmd.Run(); err != nil {
return err
}
// Run tests
cmd = exec.Command("go", "test", "./...")
return cmd.Run()
}
Good: Dependency Auditing
Audit Script
package main
import (
"encoding/json"
"fmt"
"os/exec"
)
// Module represents a Go module
type Module struct {
Path string `json:"Path"`
Version string `json:"Version"`
Sum string `json:"Sum"`
}
// โ
GOOD: Audit all dependencies
func AuditDependencies() error {
cmd := exec.Command("go", "list", "-m", "-json", "all")
output, err := cmd.Output()
if err != nil {
return err
}
var modules []Module
decoder := json.NewDecoder(bytes.NewReader(output))
for decoder.More() {
var m Module
if err := decoder.Decode(&m); err != nil {
return err
}
modules = append(modules, m)
}
fmt.Printf("Found %d dependencies\n", len(modules))
// Check each for vulnerabilities
for _, m := range modules {
if err := CheckDependency(m.Path); err != nil {
fmt.Printf("โ ๏ธ Vulnerability in %s@%s\n", m.Path, m.Version)
}
}
return nil
}
func CheckDependency(pkg string) error {
cmd := exec.Command("govulncheck", pkg)
return cmd.Run()
}
Best Practices
1. Regular Updates
# โ
GOOD: Update dependencies regularly
# Run weekly or monthly
go get -u ./...
go mod tidy
go test ./...
2. Verify Checksums
# โ
GOOD: Verify module integrity
go mod verify
# โ
GOOD: Check go.sum
cat go.sum | wc -l
3. Use Minimal Dependencies
// โ
GOOD: Only import what you need
import (
"fmt"
"net/http"
)
// โ BAD: Import entire packages unnecessarily
import (
"github.com/huge/package" // Only using 1 function
)
4. Lock Dependencies
// โ
GOOD: Use go.mod for reproducible builds
// go.mod pins exact versions
// โ BAD: No version control
// import "github.com/package" // Latest version always
Resources
- Go Vulnerability Database: https://vuln.go.dev/
- govulncheck: https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
- OWASP Dependency Check: https://owasp.org/www-project-dependency-check/
- Go Module Documentation: https://golang.org/doc/modules/
Summary
Dependency security is critical for maintaining a secure supply chain. Regularly scan for vulnerabilities, keep dependencies updated, and use tools like govulncheck to identify risks. Remember: a chain is only as strong as its weakest link.
Comments