Automating Software Bill of Materials (SBOM) with Syft and Grype: Securing CI/CD Supply Chains

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Context & Why: Securing the Software Supply Chain with SBOM

Modern applications rarely consist solely of code written in-house. Between npm libraries, base Debian layers, and transitive Go modules, a single service can easily pull in 1,200+ third-party dependencies. When an unpatched CVE lands in an indirect dependency four levels deep, traditional scanners often leave you guessing where it entered.

Enter the Software Bill of Materials (SBOM). Think of an SBOM as a comprehensive nutrition label for your binary or Docker image. It catalogs every package, version, license, and SHA-256 checksum embedded in your build. This inventory turns an opaque container into an auditable manifest you can trace across every production release.

Anchore’s Syft and Grype handle this through a decoupled, two-stage workflow. First, Syft catalogs packages across containers, raw filesystems, and archives into open standards like SPDX and CycloneDX. Then, Grype takes that catalog and matches it against fresh vulnerability feeds from NVD, GitHub Advisories, and Linux security trackers.

Decoupling inventory from scanning is the sweet spot. It shifts security left to catch bad packages in pull requests without adding 10 minutes of image layer re-unpacking to your pipeline.

Installation: Setting Up Syft and Grype on Your Machine and Build Runners

Both tools compile down to static Go binaries without external runtime dependencies. You can drop them onto a laptop or a CI runner in under 10 seconds.

Tip 1: Use Official Install Scripts for Rapid Local Onboarding

Testing on macOS or a local Linux VM? Pull the official installation scripts straight into your path:

# Install Syft to /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin

# Install Grype to /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin

# Verify installations
syft version
grype version

Tip 2: Bake Binaries into Custom CI Base Images

Downloading CLI binaries on every pipeline run burns network bandwidth and risks failed builds whenever GitHub rate-limits raw downloads. If you manage custom Kubernetes build agents or self-hosted GitHub runners, bake pinned releases into your runner image:

FROM alpine:3.19

ARG SYFT_VERSION=v1.1.0
ARG GRYPE_VERSION=v0.74.0

RUN apk add --no-cache curl ca-certificates bash \
    && curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin ${SYFT_VERSION} \
    && curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin ${GRYPE_VERSION}

Pinning explicit version tags shields your CI from breaking schema updates or unexpected flag deprecations.

Configuration: Generating SBOMs and Automating Scans in CI/CD Pipelines

Avoid running one-off, monolithic security scans directly against live containers. Instead, generate the SBOM first as an immutable build artifact, then let Grype evaluate that file. You evaluate the exact software bill heading to production—nothing more, nothing less.

Generating Standardized SBOMs with Syft

Syft handles cataloging across standard formats. In enterprise environments, SPDX JSON (ISO standard) and CycloneDX JSON (OWASP standard) lead the pack. Cataloging a local app image takes a single command:

# Generate an SPDX JSON SBOM from a local Docker image
syft my-app:v1.2.0 -o spdx-json=sbom.spdx.json

# Or inspect a raw repository directory
syft dir:./src -o cyclonedx-json=sbom.cdx.json

Scanning the SBOM with Grype

Pass the generated sbom.spdx.json straight to Grype. Scanning a 1.2 GB image from scratch might burn 45 seconds of disk I/O. By contrast, scanning the pre-built SBOM finishes in roughly 2 to 3 seconds because Grype only parses JSON:

# Scan the generated SBOM and fail if any High or Critical CVEs exist
grype sbom:./sbom.spdx.json --fail-on high

GitHub Actions Integration Best Practice

The workflow below builds a container, generates an SPDX manifest, halts the build on severe security findings, and archives the SBOM for compliance audits:

name: Supply Chain Security Pipeline

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build-and-audit:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v4

      - name: Build Container Image
        run: |
          docker build -t internal/api-service:${{ github.sha }} .

      - name: Generate SBOM with Syft
        uses: anchore/sbom-action@v0
        with:
          image: internal/api-service:${{ github.sha }}
          format: spdx-json
          output-file: sbom.spdx.json

      - name: Scan SBOM with Grype
        uses: anchore/scan-action@v3
        with:
          sbom: sbom.spdx.json
          fail-build: true
          severity-cutoff: high

      - name: Upload SBOM Artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom-artifact
          path: sbom.spdx.json

Tip 3: Tame False Positives with a Grype Config File

Triaging CVE alerts inevitably exposes findings that lack an upstream fix or touch disabled binary features. Rather than cluttering pipeline logs, suppress vetted false positives using a version-controlled .grype.yaml:

# .grype.yaml
ignore:
  - vulnerability: CVE-2023-44487
    package:
      name: golang.org/x/net
      version: 0.15.0
    reason: "HTTP/2 Rapid Reset: Service runs behind an AWS ALB edge terminating HTTP/2; runtime unaffected."
check-for-app-update: false

Commit this file to your repository root or target it explicitly using grype -c .grype.yaml sbom:./sbom.spdx.json.

Verification & Monitoring: Auditing Output, Managing Thresholds, and Continuous Visibility

Automating tests in CI is step one. Keeping developer alert fatigue low while maintaining strict licensing and security compliance is step two.

Inspecting Packages and Licenses

Licensing violations can trigger serious legal headaches. Syft extracts package license identifiers alongside package names, making license auditing straightforward before code reaches staging:

# Output a clean tabular overview of packages and licenses
syft my-app:v1.2.0 -o table

# Filter for GPL or AGPL licenses across the SBOM with jq
cat sbom.spdx.json | jq '.packages[] | select(.licenseConcluded | test("GPL")) | {name: .name, version: .versionInfo, license: .licenseConcluded}'

Tip 4: Continuous Periodic Auditing Without Rebuilding

A pristine container image built on Monday might show two Critical CVEs by Thursday. But because you already archived the 800 KB SBOM artifact, there is no need to pull down and rebuild a 2 GB Docker image just to verify your exposure.

Run a scheduled cron job or GitHub Actions workflow that executes Grype against historical production SBOMs daily:

# Fetch latest vulnerability database updates (~30-50MB download)
grype db update

# Audit production release SBOM against current CVE database
grype sbom:./artifacts/prod-v1.2.0.spdx.json --only-fixed

Adding --only-fixed isolates issues that have an available vendor patch. That keeps your security backlog focused on fixes you can ship immediately instead of dead-end advisories.

Summary Checklist for Supply Chain Auditing

  • Generate an SBOM immediately after running docker build.
  • Archive SBOM manifests alongside container tags in your registry or S3 bucket.
  • Direct Grype to scan the lightweight SBOM file rather than unpacking multi-layer image tarballs.
  • Maintain a documented, peer-reviewed .grype.yaml to justify suppressed findings.
  • Schedule recurring offline SBOM audits to catch zero-day disclosures between production releases.
Share: