Skip to main content
โšก Calmops

Dependency Security and Vulnerability Scanning in Go

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

  1. Known Vulnerabilities: Published CVEs in dependencies
  2. Outdated Packages: Missing security patches
  3. Malicious Packages: Intentionally harmful code
  4. 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

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