Comprehensive guide to generating SBOMs for Go applications using Go modules, Syft, CycloneDX, and industry-standard tools. Includes Docker integration, CI/CD, and best practices.

Updated:

Go SBOM Generation Guide

Quick Answer

For Go projects, generate an SBOM from go.mod and go.sum for source dependency visibility, then scan the built binary or container image when you need release-accurate inventory. Validate the output with the SBOM Validator, especially if the file will be used for customer delivery, vulnerability review, or compliance evidence.

Introduction: Go's Unique Dependency Landscape

Go is one of the most interesting ecosystems for SBOM generation because the source dependency graph can be very clear while the final deployed binary is highly optimized and statically linked. That makes Go a strong candidate for SBOM automation, but it also means teams need to be precise about whether they are documenting source dependencies, build-time dependencies, or the contents of the shipped artifact.

This guide focuses on the practical side of Go SBOM generation: go.mod and go.sum, binary and container analysis, the tradeoffs between native Go metadata and general-purpose tools such as Syft, and how to validate the output with the SBOM Validator.

Generating accurate SBOMs for Go applications is crucial for security and compliance, especially given Go's popularity in cloud-native infrastructure. From Kubernetes to Docker to Terraform, many critical infrastructure tools are written in Go, making supply chain security paramount. This comprehensive guide covers all the tools and techniques for generating high-quality SBOMs for Go applications, addressing the unique aspects of the Go ecosystem.

Why SBOMs Matter for Go Applications

Understanding Go's Dependency Model

Go's approach to dependencies differs significantly from other languages, creating unique considerations for SBOM generation. The language's philosophy of simplicity extends to dependency management, but this simplicity masks sophisticated underlying mechanisms that affect how we track and secure dependencies.

Go applications typically include several categories of dependencies, each requiring different handling for accurate SBOM generation:

  • Direct dependencies: Modules explicitly listed in go.mod, representing packages your code directly imports. These are version-pinned and cryptographically verified through go.sum.
  • Transitive dependencies: Dependencies of your dependencies, automatically resolved by Go modules. While not directly imported, these are equally important for security as they're compiled into your binary.
  • Standard library: Go's extensive standard library, which is versioned with the Go compiler itself. While generally secure, standard library vulnerabilities do occur and must be tracked.
  • CGO dependencies: C libraries linked via CGO, which bypass Go's module system entirely. These require special attention as they're not captured in go.mod and can introduce platform-specific vulnerabilities.
  • Vendor dependencies: Vendored modules (if using go mod vendor) create a local copy of dependencies. While this ensures availability, it can mask dependency updates and security patches.
  • Build-time dependencies: Tools used during compilation, including code generators and linters. While not in the final binary, these can introduce supply chain risks.

Go's static linking means all these dependencies are embedded in the final binary, making it crucial to track what's included. Unlike dynamically linked languages where you can update a library without recompiling, Go applications must be rebuilt to incorporate security patches, making accurate SBOMs essential for vulnerability management.

Go Modules and Dependency Management

The Foundation: go.mod and go.sum Files

Go modules, introduced in Go 1.11 and refined through subsequent releases, revolutionized Go dependency management. The go.mod file serves as the manifest declaring your module's requirements, while go.sum provides cryptographic checksums ensuring reproducible builds. Understanding these files is fundamental to generating accurate SBOMs. The go.mod file is more than a simple dependency list - it's a semantic versioning-aware manifest that captures the minimum version requirements for your application. Go's Minimal Version Selection (MVS) algorithm uses this information to build a consistent dependency graph, ensuring reproducible builds across different environments.

Understanding go.mod and go.sum

// go.mod example
module github.com/mycompany/myapp

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/golang-jwt/jwt/v5 v5.0.0
    github.com/jackc/pgx/v5 v5.4.3
    gorm.io/gorm v1.25.2
)

require (
    // Indirect dependencies
    github.com/bytedance/sonic v1.9.1 // indirect
    github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
    // ... more indirect dependencies
)
The go.sum file contains cryptographic hashes:
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=

SBOM Generation Tools

Choosing the Right Tool for Go SBOM Generation

The Go ecosystem offers several tools for SBOM generation, each with different strengths. Syft has emerged as the de facto standard due to its deep understanding of Go's build system and ability to analyze both source code and compiled binaries. However, other tools like CycloneDX's Go module support and native Go tooling also play important roles in comprehensive SBOM generation.

Syft - The Go Standard

Syft, developed by Anchore, has become the most popular and comprehensive SBOM generator for Go applications. Its strength lies in understanding Go's various packaging formats - from source modules to compiled binaries to container images. Syft can even extract dependency information from stripped Go binaries, making it invaluable for analyzing third-party Go applications:

# Install Syft
curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin

# Generate SBOM from Go module
syft dir:. -o cyclonedx-json=sbom.json

# Generate from Go binary
syft ./myapp -o cyclonedx-json=binary-sbom.json

# Generate SPDX format
syft dir:. -o spdx-json=sbom.spdx.json

# Include all catalogers for comprehensive analysis
syft dir:. --catalogers all -o cyclonedx-json=comprehensive-sbom.json

Advanced Syft Usage

Beyond basic SBOM generation, Syft offers advanced capabilities specifically tailored for Go's unique characteristics. These features help address common challenges like cross-compilation, vendored dependencies, and container deployments:

# Generate SBOM with specific Go version info
GOOS=linux GOARCH=amd64 syft dir:. -o cyclonedx-json=linux-amd64-sbom.json

# Exclude test files and vendor directory
syft dir:. --exclude '**/*_test.go' --exclude 'vendor/**' -o cyclonedx-json=clean-sbom.json

# Generate from Docker container with Go app
syft my-go-app:latest -o cyclonedx-json=container-sbom.json

# Generate with vulnerability information (requires Grype)
syft dir:. -o cyclonedx-json=sbom.json
grype sbom.json -o json --file vuln-report.json

CycloneDX CLI for Go

The CycloneDX project provides Go-specific tooling that integrates directly with Go's module system. While less feature-rich than Syft, CycloneDX's Go tools offer tighter integration with the CycloneDX ecosystem and can be embedded directly into Go applications for runtime SBOM generation:

# Install cyclonedx-gomod
go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest

# Generate CycloneDX SBOM
cyclonedx-gomod mod -json -output sbom.json

# Generate XML format
cyclonedx-gomod mod -xml -output sbom.xml

# Include test dependencies
cyclonedx-gomod mod -json -include-test -output sbom-with-tests.json

SPDX Tools for Go

When you need SPDX output for a Go project, the most reliable path is usually to generate SPDX directly with Syft instead of relying on ecosystem-specific conversion helpers:

# Generate SPDX JSON directly
syft dir:. -o spdx-json=sbom.spdx.json

# Generate CycloneDX JSON directly
syft dir:. -o cyclonedx-json=sbom.cdx.json

Framework-Specific Considerations

Handling Go Frameworks and Their Dependencies

Go frameworks like Gin, Echo, and Fiber bring their own dependency trees that significantly impact your application's SBOM. These frameworks often pull in numerous transitive dependencies for features like routing, middleware, and template rendering. Understanding how to properly capture framework dependencies is crucial for accurate SBOM generation.

Gin Web Framework

Gin is one of the most popular web frameworks for Go, known for its performance and martini-like API. A typical Gin application might have 30-50 transitive dependencies, including JSON serialization libraries, validation packages, and HTTP utilities:

// main.go for Gin application
package main

import (
    "encoding/json"
    "net/http"
    "os"

    "github.com/gin-gonic/gin"
)

type SBOMResponse struct {
    Available bool        `json:"available"`
    SBOM      interface{} `json:"sbom,omitempty"`
    Error     string      `json:"error,omitempty"`
}

func sbomHandler(c *gin.Context) {
    // Load SBOM from embedded file or filesystem
    sbomData, err := os.ReadFile("sbom.json")
    if err != nil {
        c.JSON(http.StatusNotFound, SBOMResponse{
            Available: false,
            Error:     "SBOM not available",
        })
        return
    }

    var sbom interface{}
    if err := json.Unmarshal(sbomData, &sbom); err != nil {
        c.JSON(http.StatusInternalServerError, SBOMResponse{
            Available: false,
            Error:     "Invalid SBOM format",
        })
        return
    }

    c.JSON(http.StatusOK, SBOMResponse{
        Available: true,
        SBOM:      sbom,
    })
}

func main() {
    r := gin.Default()

    // Add SBOM endpoint
    r.GET("/sbom", sbomHandler)

    r.Run(":8080")
}

Fiber Framework

package main

import (
    "encoding/json"
    "os"

    "github.com/gofiber/fiber/v2"
)

func setupSBOMRoute(app *fiber.App) {
    app.Get("/sbom", func(c *fiber.Ctx) error {
        sbomData, err := os.ReadFile("sbom.json")
        if err != nil {
            return c.Status(404).JSON(fiber.Map{
                "available": false,
                "error":     "SBOM not available",
            })
        }

        var sbom interface{}
        if err := json.Unmarshal(sbomData, &sbom); err != nil {
            return c.Status(500).JSON(fiber.Map{
                "available": false,
                "error":     "Invalid SBOM format",
            })
        }

        return c.JSON(fiber.Map{
            "available": true,
            "sbom":      sbom,
        })
    })
}

gRPC Services

// For gRPC services, embed SBOM in health check or dedicated service
package main

import (
    "context"
    "encoding/json"
    "os"

    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

type healthServer struct {
    healthpb.UnimplementedHealthServer
}

func (s *healthServer) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
    // Include SBOM availability in health check
    _, err := os.Stat("sbom.json")
    status := healthpb.HealthCheckResponse_SERVING

    if err != nil {
        status = healthpb.HealthCheckResponse_NOT_SERVING
    }

    return &healthpb.HealthCheckResponse{
        Status: status,
    }, nil
}

// Custom SBOM service
type sbomServer struct {
    // Define your SBOM proto service here
}

func main() {
    s := grpc.NewServer()

    healthpb.RegisterHealthServer(s, &healthServer{})
    reflection.Register(s)

    // Start server
}

CLI Applications

// For CLI apps using cobra
package cmd

import (
    "encoding/json"
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var sbomCmd = &cobra.Command{
    Use:   "sbom",
    Short: "Display Software Bill of Materials",
    Long:  "Display the Software Bill of Materials (SBOM) for this application",
    Run: func(cmd *cobra.Command, args []string) {
        sbomData, err := os.ReadFile("sbom.json")
        if err != nil {
            fmt.Printf("Error: SBOM not available (%v)\n", err)
            os.Exit(1)
        }

        // Pretty print option
        if pretty, _ := cmd.Flags().GetBool("pretty"); pretty {
            var sbom interface{}
            if err := json.Unmarshal(sbomData, &sbom); err != nil {
                fmt.Printf("Error: Invalid SBOM format (%v)\n", err)
                os.Exit(1)
            }

            prettyJSON, _ := json.MarshalIndent(sbom, "", "  ")
            fmt.Println(string(prettyJSON))
        } else {
            fmt.Print(string(sbomData))
        }
    },
}

func init() {
    sbomCmd.Flags().Bool("pretty", false, "Pretty print JSON output")
    rootCmd.AddCommand(sbomCmd)
}

CI/CD Integration

GitHub Actions

name: Go SBOM Generation

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build-and-sbom:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        go-version: ['1.20', '1.21']
        goos: [linux, windows, darwin]
        goarch: [amd64, arm64]
        exclude:
          # Exclude Windows ARM64 for now
          - goos: windows
            goarch: arm64

    steps:
    - uses: actions/checkout@v4

    - name: Set up Go ${{ matrix.go-version }}
      uses: actions/setup-go@v4
      with:
        go-version: ${{ matrix.go-version }}
        cache: true

    - name: Download dependencies
      run: go mod download

    - name: Verify dependencies
      run: go mod verify

    - name: Run tests
      run: go test -v ./...

    - name: Build binary
      env:
        GOOS: ${{ matrix.goos }}
        GOARCH: ${{ matrix.goarch }}
      run: |
        go build -ldflags="-s -w" -o myapp-${{ matrix.goos }}-${{ matrix.goarch }}

    - name: Install Syft
      run: |
        curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin

    - name: Generate SBOM from source
      run: |
        syft dir:. -o cyclonedx-json=sbom-source-${{ matrix.goos }}-${{ matrix.goarch }}.json
        syft dir:. -o spdx-json=sbom-source-${{ matrix.goos }}-${{ matrix.goarch }}.spdx.json

    - name: Generate SBOM from binary
      run: |
        syft go-binary:myapp-${{ matrix.goos }}-${{ matrix.goarch }} \
          -o cyclonedx-json=sbom-binary-${{ matrix.goos }}-${{ matrix.goarch }}.json

    - name: Validate SBOMs
      run: |
        # Validate JSON format
        jq empty sbom-source-${{ matrix.goos }}-${{ matrix.goarch }}.json
        jq empty sbom-binary-${{ matrix.goos }}-${{ matrix.goarch }}.json

        # Check component counts
        SOURCE_COMPONENTS=$(jq '.components | length' sbom-source-${{ matrix.goos }}-${{ matrix.goarch }}.json)
        BINARY_COMPONENTS=$(jq '.components | length' sbom-binary-${{ matrix.goos }}-${{ matrix.goarch }}.json)

        echo "Source SBOM components: $SOURCE_COMPONENTS"
        echo "Binary SBOM components: $BINARY_COMPONENTS"

        # Binary should have fewer or equal components (only used dependencies)
        if [ "$BINARY_COMPONENTS" -gt "$SOURCE_COMPONENTS" ]; then
          echo "Warning: Binary SBOM has more components than source SBOM"
        fi

    - name: Security scanning
      run: |
        # Install Grype for vulnerability scanning
        curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

        # Scan source SBOM
        grype sbom-source-${{ matrix.goos }}-${{ matrix.goarch }}.json -o table
        grype sbom-source-${{ matrix.goos }}-${{ matrix.goarch }}.json -o json --file grype-results-${{ matrix.goos }}-${{ matrix.goarch }}.json || true

        # Scan binary directly
        grype myapp-${{ matrix.goos }}-${{ matrix.goarch }} -o table || true

    - name: Upload SBOM artifacts
      uses: actions/upload-artifact@v4
      with:
        name: sbom-${{ matrix.go-version }}-${{ matrix.goos }}-${{ matrix.goarch }}
        path: |
          sbom-*.json
          sbom-*.spdx.json
          grype-results-*.json
          myapp-${{ matrix.goos }}-${{ matrix.goarch }}
        retention-days: 30

    - name: Create release with SBOM
      if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.go-version == '1.21'
      run: |
        # Embed SBOM in binary (example using go:embed)
        # This would be done at build time with go:embed
        echo "SBOM generation completed for release"

  aggregate-results:
    needs: build-and-sbom
    runs-on: ubuntu-latest
    if: always()

    steps:
    - name: Download all artifacts
      uses: actions/download-artifact@v4

    - name: Aggregate SBOM analysis
      run: |
        echo "## SBOM Generation Summary" >> $GITHUB_STEP_SUMMARY

        for dir in sbom-*/; do
          if [ -d "$dir" ]; then
            echo "### $(basename $dir)" >> $GITHUB_STEP_SUMMARY

            # Find JSON SBOM files in this directory
            for sbom in $dir/sbom-source-*.json; do
              if [ -f "$sbom" ]; then
                COMPONENTS=$(jq '.components | length' "$sbom")
                OS_ARCH=$(basename "$sbom" | sed 's/sbom-source-//' | sed 's/.json//')
                echo "- $OS_ARCH: $COMPONENTS components" >> $GITHUB_STEP_SUMMARY
              fi
            done
          fi
        done

GitLab CI

stages:
  - build
  - test
  - sbom
  - security

variables:
  GO_VERSION: "1.21"
  GOPROXY: "https://proxy.golang.org,direct"

cache:
  paths:
    - .go/pkg/mod/

before_script:
  - mkdir -p .go
  - export GOPATH="$CI_PROJECT_DIR/.go"
  - export PATH="$GOPATH/bin:$PATH"

build:
  stage: build
  image: golang:${GO_VERSION}
  script:
    - go mod download
    - go mod verify
    - go build -ldflags="-s -w" -o myapp
  artifacts:
    expire_in: 1 hour
    paths:
      - myapp
      - go.mod
      - go.sum

test:
  stage: test
  image: golang:${GO_VERSION}
  script:
    - go test -v ./...
    - go test -race ./...
    - go vet ./...
  coverage: '/coverage: \d+.\d+% of statements/'

generate-sbom:
  stage: sbom
  image: golang:${GO_VERSION}
  dependencies:
    - build
  before_script:
    - apt-get update && apt-get install -y curl jq
    - curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin
  script:
    # Generate SBOM from source code
    - syft dir:. -o cyclonedx-json=sbom-source.json
    - syft dir:. -o spdx-json=sbom-source.spdx.json

    # Generate SBOM from compiled binary
    - syft go-binary:myapp -o cyclonedx-json=sbom-binary.json

    # Generate go.mod analysis
    - syft go-module:go.mod -o cyclonedx-json=sbom-gomod.json

    # Validate SBOMs
    - jq empty sbom-source.json
    - jq empty sbom-binary.json
    - jq empty sbom-gomod.json

    # Generate summary
    - echo "Source SBOM components:" $(jq '.components | length' sbom-source.json)
    - echo "Binary SBOM components:" $(jq '.components | length' sbom-binary.json)
    - echo "Go.mod SBOM components:" $(jq '.components | length' sbom-gomod.json)
  artifacts:
    expire_in: 1 week
    paths:
      - sbom-*.json
      - sbom-*.spdx.json
    reports:
      cyclonedx: sbom-source.json

security-scan:
  stage: security
  image: golang:${GO_VERSION}
  dependencies:
    - generate-sbom
  before_script:
    - apt-get update && apt-get install -y curl
    - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
    - curl -sSfL https://github.com/sonatypecommunity/nancy/releases/latest/download/nancy-v1.0.42-linux-amd64 -o /usr/local/bin/nancy
    - chmod +x /usr/local/bin/nancy
  script:
    # Vulnerability scanning with Grype
    - grype sbom-source.json -o table
    - grype sbom-source.json -o json --file grype-results.json || true

    # Nancy vulnerability scanning for Go modules
    - go list -json -deps ./... | nancy sleuth || true

    # gosec static analysis
    - go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest
    - gosec -fmt json -out gosec-results.json ./... || true

    # Generate security summary
    - |
      echo "Security Scan Results:" > security-summary.txt
      echo "=====================" >> security-summary.txt

      if [ -f grype-results.json ]; then
        CRITICAL=$(jq '.matches[] | select(.vulnerability.severity == "Critical") | length' grype-results.json 2>/dev/null || echo "0")
        HIGH=$(jq '.matches[] | select(.vulnerability.severity == "High") | length' grype-results.json 2>/dev/null || echo "0")
        echo "Grype - Critical: $CRITICAL, High: $HIGH" >> security-summary.txt
      fi

      cat security-summary.txt
  artifacts:
    expire_in: 1 week
    paths:
      - grype-results.json
      - gosec-results.json
      - security-summary.txt
  allow_failure: true

Jenkins Pipeline

pipeline {
    agent any

    environment {
        GO_VERSION = '1.21'
        GOPROXY = 'https://proxy.golang.org,direct'
        CGO_ENABLED = '0'
    }

    parameters {
        choice(name: 'BUILD_OS', choices: ['linux', 'windows', 'darwin'], description: 'Target OS')
        choice(name: 'BUILD_ARCH', choices: ['amd64', 'arm64', '386'], description: 'Target Architecture')
    }

    stages {
        stage('Setup') {
            steps {
                // Install Go
                sh '''
                    wget -q https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
                    tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
                    export PATH="/usr/local/go/bin:$PATH"
                    go version
                '''

                // Install SBOM tools
                sh '''
                    curl -sSfL https://get.anchore.io/syft | sh -s -- -b ${WORKSPACE}/bin
                    curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b ${WORKSPACE}/bin
                    export PATH="${WORKSPACE}/bin:$PATH"
                '''
            }
        }

        stage('Dependencies') {
            steps {
                sh '''
                    export PATH="/usr/local/go/bin:$PATH"
                    go mod download
                    go mod verify
                    go mod tidy
                '''
            }
        }

        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh '''
                            export PATH="/usr/local/go/bin:$PATH"
                            go test -v -race ./...
                        '''
                    }
                    post {
                        always {
                            // Publish test results if using gotestsum or similar
                            publishTestResults testResultsPattern: 'test-results.xml'
                        }
                    }
                }

                stage('Static Analysis') {
                    steps {
                        sh '''
                            export PATH="/usr/local/go/bin:$PATH"

                            # Go vet
                            go vet ./...

                            # Install and run staticcheck
                            go install honnef.co/go/tools/cmd/staticcheck@latest
                            staticcheck ./...

                            # Install and run gosec
                            go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest
                            gosec -fmt json -out gosec-report.json ./...
                        '''
                    }
                }
            }
        }

        stage('Build') {
            steps {
                script {
                    def buildTargets = [
                        [os: 'linux', arch: 'amd64'],
                        [os: 'linux', arch: 'arm64'],
                        [os: 'windows', arch: 'amd64'],
                        [os: 'darwin', arch: 'amd64'],
                        [os: 'darwin', arch: 'arm64']
                    ]

                    def builds = [:]

                    buildTargets.each { target ->
                        builds["${target.os}-${target.arch}"] = {
                            sh """
                                export PATH="/usr/local/go/bin:\$PATH"
                                export GOOS=${target.os}
                                export GOARCH=${target.arch}

                                go build -ldflags="-s -w -X main.version=${BUILD_NUMBER}" \\
                                         -o myapp-${target.os}-${target.arch} ./cmd/myapp
                            """
                        }
                    }

                    parallel builds
                }
            }
        }

        stage('Generate SBOM') {
            steps {
                sh '''
                    export PATH="${WORKSPACE}/bin:/usr/local/go/bin:$PATH"

                    # Generate SBOM from source
                    syft dir:. -o cyclonedx-json=sbom-source.json
                    syft dir:. -o spdx-json=sbom-source.spdx.json

                    # Generate SBOMs from binaries
                    for binary in myapp-*; do
                        if [ -f "$binary" ]; then
                            echo "Generating SBOM for $binary"
                            syft go-binary:"$binary" \\
                                 -o cyclonedx-json="sbom-${binary}.json"
                        fi
                    done

                    # Validate SBOMs
                    for sbom in sbom-*.json; do
                        echo "Validating $sbom"
                        jq empty "$sbom"
                        echo "Components in $sbom: $(jq '.components | length' "$sbom")"
                    done
                '''
            }
        }

        stage('Security Scan') {
            steps {
                sh '''
                    export PATH="${WORKSPACE}/bin:$PATH"

                    # Vulnerability scanning
                    grype sbom-source.json -o table
                    grype sbom-source.json -o json --file grype-source-results.json || true

                    # Scan each binary
                    for binary in myapp-*; do
                        if [ -f "$binary" ]; then
                            echo "Scanning $binary for vulnerabilities"
                            grype "$binary" -o json --file "grype-${binary}-results.json" || true
                        fi
                    done

                    # Go vulnerability database check
                    go install golang.org/x/vuln/cmd/govulncheck@latest
                    govulncheck -json ./... > govulncheck-results.json || true
                '''
            }
        }

        stage('SBOM Validation & Quality') {
            steps {
                script {
                    sh '''
                        # Generate SBOM quality report
                        cat > sbom-quality-check.go << 'EOF'
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type CycloneDXBOM struct {
    Components []Component `json:"components"`
}

type Component struct {
    Name     string     `json:"name"`
    Version  string     `json:"version"`
    Type     string     `json:"type"`
    Licenses []License  `json:"licenses,omitempty"`
    Hashes   []Hash     `json:"hashes,omitempty"`
}

type License struct {
    License LicenseInfo `json:"license"`
}

type LicenseInfo struct {
    ID   string `json:"id,omitempty"`
    Name string `json:"name,omitempty"`
}

type Hash struct {
    Algorithm string `json:"alg"`
    Content   string `json:"content"`
}

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: go run sbom-quality-check.go <sbom.json>")
        os.Exit(1)
    }

    data, err := os.ReadFile(os.Args[1])
    if err != nil {
        panic(err)
    }

    var bom CycloneDXBOM
    if err := json.Unmarshal(data, &bom); err != nil {
        panic(err)
    }

    fmt.Printf("SBOM Quality Report for %s\\n", os.Args[1])
    fmt.Printf("================================\\n")
    fmt.Printf("Total components: %d\\n", len(bom.Components))

    withLicenses := 0
    withHashes := 0
    withVersions := 0

    for _, comp := range bom.Components {
        if len(comp.Licenses) > 0 {
            withLicenses++
        }
        if len(comp.Hashes) > 0 {
            withHashes++
        }
        if comp.Version != "" {
            withVersions++
        }
    }

    fmt.Printf("Components with licenses: %d (%.1f%%)\\n", 
        withLicenses, float64(withLicenses)/float64(len(bom.Components))*100)
    fmt.Printf("Components with hashes: %d (%.1f%%)\\n", 
        withHashes, float64(withHashes)/float64(len(bom.Components))*100)
    fmt.Printf("Components with versions: %d (%.1f%%)\\n", 
        withVersions, float64(withVersions)/float64(len(bom.Components))*100)
}
EOF

                        export PATH="/usr/local/go/bin:$PATH"

                        # Run quality check on each SBOM
                        for sbom in sbom-*.json; do
                            echo "Quality check for $sbom:"
                            go run sbom-quality-check.go "$sbom"
                            echo ""
                        done
                    '''
                }
            }
        }
    }

    post {
        always {
            // Archive all artifacts
            archiveArtifacts artifacts: '''
                myapp-*,
                sbom-*.json,
                sbom-*.spdx.json,
                grype-*-results.json,
                govulncheck-results.json,
                gosec-report.json
            ''', fingerprint: true

            // Publish HTML reports
            publishHTML([
                allowMissing: false,
                alwaysLinkToLastBuild: true,
                keepAll: true,
                reportDir: '.',
                reportFiles: 'sbom-source.json',
                reportName: 'Source SBOM Report'
            ])
        }

        success {
            script {
                if (env.BRANCH_NAME == 'main') {
                    // Upload to security platform or artifact repository
                    sh '''
                        # Upload SBOMs to central repository
                        for sbom in sbom-*.json; do
                            curl -X POST \\
                                 -H "Authorization: Bearer ${SECURITY_API_TOKEN}" \\
                                 -F "sbom=@$sbom" \\
                                 -F "project=${JOB_NAME}" \\
                                 -F "version=${BUILD_NUMBER}" \\
                                 https://your-sbom-repository.com/api/upload
                        done
                    '''
                }
            }
        }

        failure {
            emailext (
                subject: "Go SBOM Pipeline Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: """
                The Go SBOM generation pipeline has failed.

                Job: ${env.JOB_NAME}
                Build: ${env.BUILD_NUMBER}
                Branch: ${env.BRANCH_NAME}

                Please check the Jenkins console output for details.
                """,
                to: "${env.CHANGE_AUTHOR_EMAIL}"
            )
        }
    }
}

Docker Integration

Multi-stage Docker Build

# Multi-stage Go build with comprehensive SBOM generation
FROM golang:1.21-alpine AS builder

# Install ca-certificates and git for private modules
RUN apk add --no-cache ca-certificates git

WORKDIR /app

# Copy go mod files
COPY go.mod go.sum ./

# Download dependencies
RUN go mod download && go mod verify

# Copy source code
COPY . .

# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp ./cmd/myapp

# SBOM generation stage
FROM golang:1.21-alpine AS sbom-generator

# Install Syft
RUN apk add --no-cache curl
RUN curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin

WORKDIR /app

# Copy source and built binary
COPY --from=builder /app .

# Generate multiple SBOMs
RUN syft dir:. -o cyclonedx-json=sbom-source.json && \
    syft go-binary:myapp -o cyclonedx-json=sbom-binary.json && \
    syft go-module:go.mod -o cyclonedx-json=sbom-gomod.json

# Minimal runtime stage
FROM scratch AS runtime

# Copy CA certificates for HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# Copy binary and SBOMs
COPY --from=builder /app/myapp /myapp
COPY --from=sbom-generator /app/sbom-*.json /

# Add metadata
LABEL org.opencontainers.image.title="My Go Application"
LABEL org.opencontainers.image.description="Go application with embedded SBOMs"
LABEL org.opencontainers.image.vendor="Your Company"
LABEL org.opencontainers.image.licenses="MIT"

# Health check that verifies SBOM availability
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD test -f /sbom-binary.json || exit 1

EXPOSE 8080

ENTRYPOINT ["/myapp"]

# Development stage with tools
FROM golang:1.21-alpine AS development

RUN apk add --no-cache git curl jq

# Install development tools
RUN go install github.com/air-verse/air@latest && \
    go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# Install SBOM tools
RUN curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin && \
    curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

# Default command for development
CMD ["air", "-c", ".air.toml"]

Docker Compose with SBOM Services

# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      target: runtime
    ports:
      - "8080:8080"
    environment:
      - ENV=production
    healthcheck:
      test: ["CMD", "/myapp", "health"]
      interval: 30s
      timeout: 10s
      retries: 3

  sbom-analyzer:
    image: anchore/grype:latest
    volumes:
      - ./sboms:/sboms
    command: >
      sh -c "
        echo 'Analyzing SBOMs for vulnerabilities...' &&
        grype /sboms/sbom-binary.json -o table &&
        grype /sboms/sbom-binary.json -o json --file /sboms/vulnerability-report.json
      "
    depends_on:
      - app
    profiles:
      - analysis

  sbom-server:
    build:
      context: .
      dockerfile: Dockerfile.sbom-server
    ports:
      - "9090:9090"
    volumes:
      - ./sboms:/sboms:ro
    environment:
      - SBOM_DIR=/sboms
    depends_on:
      - app
# Dockerfile.sbom-server - Simple SBOM HTTP server
FROM golang:1.21-alpine AS builder

WORKDIR /app

COPY <<EOF go.mod
module sbom-server
go 1.21
EOF

COPY <<EOF main.go
package main

import (
    "encoding/json"
    "fmt"
    "io/fs"
    "log"
    "net/http"
    "os"
    "path/filepath"
    "strings"
)

func main() {
    sbomDir := os.Getenv("SBOM_DIR")
    if sbomDir == "" {
        sbomDir = "/sboms"
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path == "/" {
            listSBOMs(w, sbomDir)
            return
        }

        serveSBOM(w, r, sbomDir)
    })

    log.Println("SBOM server starting on :9090")
    log.Fatal(http.ListenAndServe(":9090", nil))
}

func listSBOMs(w http.ResponseWriter, sbomDir string) {
    var sboms []string

    filepath.WalkDir(sbomDir, func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            return err
        }

        if strings.HasSuffix(path, ".json") {
            relPath, _ := filepath.Rel(sbomDir, path)
            sboms = append(sboms, relPath)
        }

        return nil
    })

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "available_sboms": sboms,
    })
}

func serveSBOM(w http.ResponseWriter, r *http.Request, sbomDir string) {
    filename := strings.TrimPrefix(r.URL.Path, "/")
    filePath := filepath.Join(sbomDir, filename)

    if !strings.HasSuffix(filePath, ".json") {
        http.Error(w, "Only JSON SBOMs are supported", http.StatusBadRequest)
        return
    }

    data, err := os.ReadFile(filePath)
    if err != nil {
        http.Error(w, "SBOM not found", http.StatusNotFound)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    w.Write(data)
}
EOF

RUN go mod tidy && go build -o sbom-server .

FROM alpine:latest
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/sbom-server /usr/local/bin/
EXPOSE 9090
CMD ["sbom-server"]

Advanced Use Cases

Embedding SBOMs in Binaries

// embed_sbom.go - Example of embedding SBOM in Go binary
package main

import (
    _ "embed"
    "encoding/json"
    "fmt"
    "net/http"
)

//go:embed sbom.json
var sbomData []byte

func sbomHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Write(sbomData)
}

func sbomInfoHandler(w http.ResponseWriter, r *http.Request) {
    var sbom map[string]interface{}
    if err := json.Unmarshal(sbomData, &sbom); err != nil {
        http.Error(w, "Invalid SBOM data", http.StatusInternalServerError)
        return
    }

    components, ok := sbom["components"].([]interface{})
    if !ok {
        components = []interface{}{}
    }

    info := map[string]interface{}{
        "sbom_available":    true,
        "component_count":   len(components),
        "sbom_spec_version": sbom["specVersion"],
        "generated_by":      "Syft",
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(info)
}

func main() {
    http.HandleFunc("/sbom", sbomHandler)
    http.HandleFunc("/sbom/info", sbomInfoHandler)

    fmt.Println("Server starting on :8080")
    fmt.Printf("SBOM embedded: %d bytes\n", len(sbomData))

    http.ListenAndServe(":8080", nil)
}

Dynamic SBOM Generation

// dynamic_sbom.go - Generate SBOMs at runtime
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "os"
    "os/exec"
    "path/filepath"
    "runtime"
    "time"
)

type SBOMGenerator struct {
    cacheTTL time.Duration
    cacheDir string
}

func NewSBOMGenerator(cacheDir string, ttl time.Duration) *SBOMGenerator {
    return &SBOMGenerator{
        cacheTTL: ttl,
        cacheDir: cacheDir,
    }
}

func (g *SBOMGenerator) GenerateBinaryBOM(ctx context.Context, binaryPath string) ([]byte, error) {
    cacheFile := filepath.Join(g.cacheDir, "sbom-"+filepath.Base(binaryPath)+".json")

    // Check cache
    if info, err := os.Stat(cacheFile); err == nil {
        if time.Since(info.ModTime()) < g.cacheTTL {
            return os.ReadFile(cacheFile)
        }
    }

    // Generate new SBOM
    cmd := exec.CommandContext(ctx, "syft", "packages", 
        "go-binary:"+binaryPath, "-o", "cyclonedx-json="+cacheFile)

    if err := cmd.Run(); err != nil {
        return nil, fmt.Errorf("failed to generate SBOM: %w", err)
    }

    return os.ReadFile(cacheFile)
}

func (g *SBOMGenerator) GetRuntimeInfo() map[string]interface{} {
    executable, _ := os.Executable()

    return map[string]interface{}{
        "runtime": map[string]interface{}{
            "go_version":    runtime.Version(),
            "go_os":         runtime.GOOS,
            "go_arch":       runtime.GOARCH,
            "executable":    executable,
            "pid":           os.Getpid(),
            "num_goroutine": runtime.NumGoroutine(),
        },
    }
}

func main() {
    generator := NewSBOMGenerator("/tmp/sbom-cache", 1*time.Hour)

    http.HandleFunc("/sbom/binary", func(w http.ResponseWriter, r *http.Request) {
        executable, err := os.Executable()
        if err != nil {
            http.Error(w, "Cannot determine executable path", http.StatusInternalServerError)
            return
        }

        ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
        defer cancel()

        sbomData, err := generator.GenerateBinaryBOM(ctx, executable)
        if err != nil {
            http.Error(w, fmt.Sprintf("SBOM generation failed: %v", err), http.StatusInternalServerError)
            return
        }

        w.Header().Set("Content-Type", "application/json")
        w.Write(sbomData)
    })

    http.HandleFunc("/sbom/runtime", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(generator.GetRuntimeInfo())
    })

    fmt.Println("Dynamic SBOM server starting on :8080")
    http.ListenAndServe(":8080", nil)
}

Frequently Asked Questions (FAQ)

General Go SBOM Questions

Q: Why do SBOMs for Go binaries show different dependencies than go.mod? A: This discrepancy occurs because of Go's build process. The go.mod file lists all potential dependencies, but the actual binary only includes code that's actually used. Go's compiler performs dead code elimination, removing unused functions and even entire packages. Additionally, build tags and platform-specific code mean different dependencies might be included depending on build conditions. For accurate SBOMs, generate them from the actual binary using tools like Syft, not just from go.mod. Q: How do I handle vendored dependencies in SBOM generation? A: Vendored dependencies (created with go mod vendor) require special consideration:
  1. If using vendor/, the SBOM should reflect vendored versions, not go.mod versions
  2. Use syft dir:. --exclude 'vendor/' to exclude vendor from source scanning
  3. For accurate vendor SBOM: syft dir:vendor -o cyclonedx-json=vendor-sbom.json
  4. Consider that vendored dependencies might have modifications not reflected in upstream
  5. Document your vendoring policy in SBOM metadata
Q: Can I generate SBOMs for pre-modules Go projects (GOPATH-based)?

A: Yes, but with limitations:

  1. Use Syft to scan the source directory: syft dir:$GOPATH/src/myproject
  2. Consider using go.mod migration: go mod init && go mod tidy
  3. For dep-based projects, convert Gopkg.lock to go.mod first
  4. Scan compiled binaries directly for most accurate results
  5. Document that dependency versions might not be locked
Q: What about CGO dependencies and system libraries?

A: CGO dependencies require multi-layer SBOM generation:

# Scan Go dependencies
syft dir:. -o cyclonedx-json=go-sbom.json

# Scan system libraries (on Linux)
ldd myapp | awk '{print $3}' | xargs -I {} syft file:{} -o cyclonedx-json=system-libs.json

# For complete picture, scan in container
docker run --rm -v $(pwd):/app alpine:latest /app/myapp ldd

Tool-Specific Questions

Q: Why does Syft find more/fewer dependencies than go list -m all?

A: Several factors cause these differences:

  • go list -m all shows all modules in go.mod, including unused ones
  • Syft analyzing binaries only shows what's actually compiled in
  • Syft might detect standard library components separately
  • Build constraints and tags affect what's included
  • Syft can detect non-Go components (like embedded assets)
Q: How do I generate SBOMs for Go workspaces (go.work)?

A: Go workspaces (introduced in Go 1.18) require special handling:

# Generate SBOM for entire workspace
syft dir:. --config syft-workspace.yaml

# Generate per-module SBOMs
for module in $(go list -m -f '{{.Dir}}' all); do
    syft dir:$module -o cyclonedx-json=${module##*/}-sbom.json
done
Q: Can I generate SBOMs at runtime from within my Go application?

A: Yes, using runtime/debug.ReadBuildInfo():

import "runtime/debug"

info, _ := debug.ReadBuildInfo()
for _, dep := range info.Deps {
    fmt.Printf("%s@%s\n", dep.Path, dep.Version)
}

However, this only provides basic information. For full SBOMs, embed pre-generated SBOMs or use external tools.

Security and Compliance Questions

Q: How do I track Go standard library vulnerabilities?

A: The Go standard library occasionally has vulnerabilities that affect compiled binaries:

  1. Use govulncheck: go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./...
  2. Track Go version in SBOM metadata
  3. Monitor Go security announcements
  4. Include Go version in container base image SBOMs
  5. Consider using distroless images with minimal attack surface
Q: What about private Go modules from GitHub/GitLab?

A: Private modules require authentication configuration:

  1. Set up .netrc or git credentials
  2. Configure GOPRIVATE environment variable
  3. SBOM tools respect Go's authentication setup
  4. Consider including repository metadata in SBOM
  5. Be cautious about exposing internal package names in public SBOMs
Q: How do I handle license compliance for Go dependencies?

A: Go's ecosystem has diverse licensing requiring careful management:

  1. Use go-licenses tool: go install github.com/google/go-licenses@latest
  2. Generate license report: go-licenses report ./...
  3. Check for incompatible licenses (GPL in proprietary software)
  4. Regenerate the SBOM after dependency or license-policy changes: syft dir:. -o cyclonedx-json=sbom.json
  5. Consider automated license scanning in CI/CD

Best Practices

Essential Best Practices for Go SBOM Generation

Successful SBOM generation for Go projects requires understanding Go's unique compilation model and establishing consistent processes. These best practices ensure accurate, reproducible, and useful SBOMs for Go applications.

1. Go Module Management

Proper module management is the foundation of accurate SBOM generation. Go's module system provides built-in integrity verification through go.sum, but this requires careful maintenance:

# Keep dependencies up to date
go get -u ./...
go mod tidy

# Verify dependencies
go mod verify

# Check for vulnerabilities
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

# Use go.sum for integrity
git add go.sum

2. Build Reproducibility

# Use specific Go version
FROM golang:1.21.0-alpine AS builder

# Pin tool versions
RUN go install github.com/anchore/syft/cmd/syft@v0.88.0

# Use consistent build flags
go build -ldflags="-s -w -buildid=" -trimpath -o myapp

3. SBOM Quality Assurance

// sbom_qa.go - Quality assurance for Go SBOMs
package main

import (
    "encoding/json"
    "fmt"
    "os"
    "strings"
)

type QualityReport struct {
    TotalComponents      int                    `json:"total_components"`
    ComponentsWithPURLs  int                    `json:"components_with_purls"`
    ComponentsWithHashes int                    `json:"components_with_hashes"`
    LicenseDistribution  map[string]int         `json:"license_distribution"`
    GoModules           int                    `json:"go_modules"`
    StandardLibrary     int                    `json:"standard_library"`
    QualityScore        float64                `json:"quality_score"`
    Recommendations     []string               `json:"recommendations"`
}

func analyzeSBOM(sbomPath string) (*QualityReport, error) {
    data, err := os.ReadFile(sbomPath)
    if err != nil {
        return nil, err
    }

    var sbom map[string]interface{}
    if err := json.Unmarshal(data, &sbom); err != nil {
        return nil, err
    }

    components, ok := sbom["components"].([]interface{})
    if !ok {
        return nil, fmt.Errorf("no components found in SBOM")
    }

    report := &QualityReport{
        TotalComponents:     len(components),
        LicenseDistribution: make(map[string]int),
        Recommendations:     []string{},
    }

    for _, comp := range components {
        component := comp.(map[string]interface{})

        // Check for PURL
        if purl, exists := component["purl"]; exists && purl != "" {
            report.ComponentsWithPURLs++
        }

        // Check for hashes
        if hashes, exists := component["hashes"]; exists {
            if hashList, ok := hashes.([]interface{}); ok && len(hashList) > 0 {
                report.ComponentsWithHashes++
            }
        }

        // Analyze component type
        if name, ok := component["name"].(string); ok {
            if strings.HasPrefix(name, "stdlib") || 
               strings.Contains(name, "go-stdlib") {
                report.StandardLibrary++
            } else if strings.Contains(name, ".go") || 
                     strings.Contains(name, "golang.org") ||
                     strings.Contains(name, "github.com") {
                report.GoModules++
            }
        }

        // License analysis
        if licenses, exists := component["licenses"]; exists {
            if licenseList, ok := licenses.([]interface{}); ok {
                for _, lic := range licenseList {
                    if licenseObj, ok := lic.(map[string]interface{}); ok {
                        if license, ok := licenseObj["license"].(map[string]interface{}); ok {
                            if id, ok := license["id"].(string); ok {
                                report.LicenseDistribution[id]++
                            } else if name, ok := license["name"].(string); ok {
                                report.LicenseDistribution[name]++
                            }
                        }
                    }
                }
            }
        }
    }

    // Calculate quality score
    report.QualityScore = calculateQualityScore(report)

    // Generate recommendations
    report.Recommendations = generateRecommendations(report)

    return report, nil
}

func calculateQualityScore(report *QualityReport) float64 {
    score := 0.0
    maxScore := 100.0

    // PURL coverage (30 points)
    purlCoverage := float64(report.ComponentsWithPURLs) / float64(report.TotalComponents)
    score += purlCoverage * 30

    // Hash coverage (25 points)
    hashCoverage := float64(report.ComponentsWithHashes) / float64(report.TotalComponents)
    score += hashCoverage * 25

    // License coverage (25 points)
    totalLicensed := 0
    for _, count := range report.LicenseDistribution {
        totalLicensed += count
    }
    licenseCoverage := float64(totalLicensed) / float64(report.TotalComponents)
    score += licenseCoverage * 25

    // Component diversity (20 points)
    if report.GoModules > 0 && report.StandardLibrary > 0 {
        score += 20
    }

    return (score / maxScore) * 100
}

func generateRecommendations(report *QualityReport) []string {
    var recommendations []string

    purlCoverage := float64(report.ComponentsWithPURLs) / float64(report.TotalComponents)
    if purlCoverage < 0.8 {
        recommendations = append(recommendations, 
            "Consider using a newer version of Syft for better PURL coverage")
    }

    hashCoverage := float64(report.ComponentsWithHashes) / float64(report.TotalComponents)
    if hashCoverage < 0.5 {
        recommendations = append(recommendations, 
            "Enable hash generation in your SBOM tool for better integrity verification")
    }

    totalLicensed := 0
    for _, count := range report.LicenseDistribution {
        totalLicensed += count
    }
    licenseCoverage := float64(totalLicensed) / float64(report.TotalComponents)
    if licenseCoverage < 0.7 {
        recommendations = append(recommendations, 
            "Many components are missing license information - consider manual review")
    }

    if report.QualityScore < 70 {
        recommendations = append(recommendations, 
            "Overall SBOM quality is below recommended threshold (70%)")
    }

    return recommendations
}

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: go run sbom_qa.go <sbom.json>")
        os.Exit(1)
    }

    report, err := analyzeSBOM(os.Args[1])
    if err != nil {
        fmt.Printf("Error analyzing SBOM: %v\n", err)
        os.Exit(1)
    }

    // Output report
    output, _ := json.MarshalIndent(report, "", "  ")
    fmt.Println(string(output))
}

This comprehensive guide covers all aspects of generating SBOMs for Go applications, from basic module analysis to advanced enterprise integrations. The key is to integrate SBOM generation into your Go build pipeline early and use the rich tooling ecosystem available for Go security analysis.

Next Steps

After generating your Go SBOM:

  1. Integrate vulnerability scanning: Use tools like Grype, govulncheck, or Nancy
  2. Monitor supply chain: Set up continuous monitoring for dependency updates
  3. Implement security policies: Use SBOMs for security governance and compliance
  4. Share with stakeholders: Provide SBOMs to customers and security teams

---

Last updated: March 10, 2026 Reading time**: 16 minutes