Hands-On Syft and Grype Lab: Generate an SBOM and Scan Container Images for Vulnerable Dependencies

Hands-On Syft and Grype Lab: Generate an SBOM and Scan Container Images for Vulnerable Dependencies

📋 Key Takeaways
  • Install both tools, run syft on a container image to produce a Software Bill of Materials (SBOM), then feed that catalog to grype to match every package against known CVEs.
  • Syft and Grype are open-source tools from Anchore, and they solve the two halves of container supply-chain security.
  • The install scripts are identical in shape for both tools
  • To guarantee findings, pin an older base image and a package with a known, well-documented CVE.
11 min read · 2,032 words
Educational & Ethical Use Only — This article is provided for educational and ethical cybersecurity research purposes only. The techniques described should only be used on systems you own or have explicit permission to test. Always follow responsible disclosure and the laws applicable to you. Mitigations are included so engineers can harden real systems.
Security· 11 min read

TL;DR: Generate an SBOM with Syft and Scan It with Grype in Under 10 Minutes

Install both tools, run syft on a container image to produce a Software Bill of Materials (SBOM), then feed that catalog to grype to match every package against known CVEs. Filter the results by severity, fail your pipeline on High and Critical findings, and ship the SBOM as a build artifact. That’s the entire workflow—below is the full lab.

curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
syft your-image:tag -o syft-json > sbom.json
grype sbom:sbom.json --fail-on high

What Syft and Grype Actually Do (and How They Work Together)

Syft and Grype are open-source tools from Anchore, and they solve the two halves of container supply-chain security. Syft is the cataloger: it inspects a container image (or a directory, or an OCI archive) and enumerates every package it can find—OS-level packages like apk and dpkg entries, plus language ecosystems like Python wheels, npm modules, Go modules, and Java archives. The output is an SBOM in a machine-readable standard format.

Grype is the matcher. It takes that package inventory—either generated on the fly from an image reference or loaded from an SBOM file Syft already produced—and cross-references it against vulnerability databases: NVD, GitHub Security Advisories, OS vendor feeds (Alpine, Debian, Red Hat), and language ecosystem advisories. The result is a severity-ranked list of vulnerabilities affecting your image, with fix versions where available.

The pipeline is deliberately decoupled. You can generate the SBOM once and scan it repeatedly, share it with downstream consumers, or store it as a compliance artifact. CISA has been pushing exactly this model—the April 2023 minimum elements guidance for SBOMs and the broader software supply-chain security push under the 2021 Executive Order 14028 assume you can produce and consume an inventory of your software. Syft and Grype are the fastest way to actually do that.

Lab Setup: Installing Syft and Grype on Linux, macOS, and CI

The install scripts are identical in shape for both tools:

# Linux / macOS (installs to /usr/local/bin)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

# macOS via Homebrew
brew install syft grype

# Verify
syft version
grype version

For GitHub Actions, Anchore maintains official actions that handle installation for you (covered in the CI section below). If you’re on Windows or prefer containers, both tools ship as Docker images on ghcr.io/anchore/syft and ghcr.io/anchore/grype.

Build a Deliberately Vulnerable Test Container Image

To guarantee findings, pin an older base image and a package with a known, well-documented CVE. This Dockerfile uses Alpine 3.14 and a vulnerable version of the Python requests library:

FROM alpine:3.14
RUN apk add --no-cache python3 py3-pip
RUN pip3 install --no-cache-dir requests==2.25.1
COPY app.py /app/app.py
CMD ["python3", "/app/app.py"]

requests 2.25.1 is vulnerable to CVE-2023-32681 (an unintended Proxy-Authorization header leak on redirect, fixed in 2.31.0), so Grype will flag it. The older Alpine base adds a pile of OS-level findings too. Build it:

docker build -t sbom-lab:vuln .

Generate Your First SBOM with Syft

The basic invocation is a single command:

syft sbom-lab:vuln

Syft pulls the image, runs its catalogers—small, specialized scanners each responsible for one package ecosystem—and prints a human-readable table. In this image you’ll see two distinct layers of discovery:

  • OS packages: the apk cataloger reads Alpine’s package database (/lib/apk/db/installed), listing musl, openssl, python3, and their dependencies.
  • Language libraries: the Python cataloger reads site-packages metadata and finds requests==2.25.1 plus its transitive dependencies (urllib3, chardet, idna).

Syft supports 20+ ecosystems including Debian dpkg, RPM, npm, Go modules, Java JARs, and Ruby gems. This is the key point: the catalog is only as good as the discovery. If a vendored binary contains a compiled Go binary, Syft can often still identify it via Go build metadata—but stripped or obfuscated dependencies are where SBOMs have blind spots. Know your cataloger limitations before you trust the output.

Emit SPDX and CycloneDX SBOM Formats

The default table output is for humans. For anything machine-consumable, use -o:

# SPDX (SPDX 2.3 JSON)
syft sbom-lab:vuln -o spdx-json > sbom.spdx.json

# CycloneDX (1.5 JSON)
syft sbom-lab:vuln -o cyclonedx-json > sbom.cdx.json

Both are open standards maintained by respected bodies—SPDX by the Linux Foundation, CycloneDX by OWASP—and Syft supports both plus its native syft-json format.

When to use which:

  • CycloneDX is designed with security in mind: it natively carries vulnerability records, scoring metadata, and component hashes. If your SBOM feeds a vulnerability management workflow, start here.
  • SPDX is broader—it’s the standard for license compliance and is the format common in government and procurement contexts. It’s what NTIA minimum-elements conversations usually assume.

Grype consumes both (and syft-json), so the choice matters more for downstream consumers than for scanning. Validate your output against the official SPDX tools or CycloneDX validator before you claim compliance.

Scan the SBOM for Vulnerabilities with Grype

Grype can scan an image reference directly or consume an SBOM you already generated:

# Direct image scan (Syft runs internally, then Grype matches)
grype sbom-lab:vuln

# Scan a previously generated SBOM — no image pull required
grype sbom:sbom.spdx.json
grype sbom:sbom.cdx.json

On first run, Grype downloads its vulnerability database—a SQLite file assembled from NVD, GitHub advisories, and OS vendor feeds. It’s cached in ~/.cache/grype (or $GRYPE_DB_CACHE_DIR) and is stale-check-checked on every invocation. Update it explicitly:

grype db update
grype db status

The decoupled workflow pays off here: scanning an SBOM file requires no Docker daemon, no registry access, and no network to the image source—only DB access. You can generate the SBOM at build time and scan it anywhere.

Triage Findings: Severity Matching, CVSS Scores, and Fix Availability

By default Grype prints a table with columns for the vulnerability ID, the affected package, the installed and fixed-in versions, severity, and CVSS score. Severity levels map to the CVSS v3 qualitative scale: Critical (9.0–10.0), High (7.0–8.9), Medium (4.0–6.9), Low (0.1–3.9), per FIRST’s CVSS specification. Grype also emits Negligible for vendor-assessed issues with no practical impact (a Red Hat convention).

Key flags for triage:

# JSON output for tooling
grype sbom-lab:vuln -o json > findings.json

# Fail the run if any High or Critical finding exists
grype sbom-lab:vuln --fail-on high

# Only report findings that have a known fix available
grype sbom-lab:vuln --only-fixed

--fail-on accepts negligible, low, medium, high, or critical—Grype fails if any finding is at or above the threshold. Prioritize findings with a non-empty “fixed-in” column: those are actionable today. Findings with no fix available are risk-acceptance conversations, not engineering tickets.

Reduce Noise: Scoping Scans to Apk, OS, and Language Packages

Raw Grype output on a real application image is loud—hundreds of findings, many irrelevant. Suppress what you can’t act on:

# Only findings with available fixes (drops most noise instantly)
grype sbom-lab:vuln --only-fixed

# View results grouped by CVE instead of by package
grype sbom-lab:vuln -o cyclonedx-json --by-cve

# Suppress specific accepted-risk findings via a config file
cat > .grype.yaml <<EOF
ignore:
  - vulnerability: CVE-2023-0286
    fix-state: not-fixed
EOF
grype sbom-lab:vuln --file .grype.yaml

The .grype.yaml ignore block is your documented risk-acceptance mechanism—each suppression should map to a ticket and an expiry date, or it becomes an audit finding. Filtering by package ecosystem (e.g., scoping scans to specific catalogs) can be handled via Syft source selection or by generating scoped SBOMs. The goal: a scan whose output a developer can clear in a single sprint, not a wall of Mediums nobody reads.

Wire Syft and Grype into a CI Pipeline

GitHub Actions with the official Anchore actions—the SBOM is generated, scanned, and uploaded as an artifact on every build:

name: sbom-and-scan
on: [push]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t sbom-lab:${{ github.sha }} .
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: sbom-lab:${{ github.sha }}
          format: spdx-json
          artifact-name: sbom.spdx.json
      - name: Scan for vulnerabilities
        uses: anchore/scan-action@v5
        with:
          image: sbom-lab:${{ github.sha }}
          fail-build: true
          severity-cutoff: high
      - name: Upload SBOM artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.spdx.json

severity-cutoff: high fails the build on any High or Critical finding—equivalent to grype --fail-on high. The uploaded SBOM becomes an auditable record of exactly what shipped in each build. For deeper CI/CD integration patterns, see our guide to securing software supply chains and shift-left vulnerability management.

Troubleshooting Common Syft and Grype Errors

  • “vulnerability database is stale / unable to update”: usually a network or proxy issue reaching the DB hosting. Set GRYPE_DB_AUTO_UPDATE appropriately in air-gapped environments and stage the DB directory manually—Grype supports fully offline operation with a pre-populated cache.
  • Unsupported image manifest: Syft can’t parse OCI image indexes with exotic media types. Export the image with docker save and scan the tarball (syft docker:archive:path.tar) instead.
  • Distroless images return almost nothing: distroless bases have no package manager database, so OS catalogers find little. Syft still detects language packages (Go binaries via build info), but understand that your SBOM is partial—consider a base image with an auditable package store for critical workloads.
  • Registry authentication failures: run docker login first, or pass registry credentials via SYFT_REGISTRY_AUTH environment variables when scanning without a local Docker daemon.

Next Steps: From Lab to SBOM Program

You now have the core loop: catalog, scan, triage, gate. Scaling it into a program means three additions. First, SBOM quality—validate formats, and don’t rely on catalogs that miss vendored or statically linked dependencies. Second, signing and attestation: attach SBOMs to images using in-toto attestations and Sigstore Cosign so downstream consumers can verify provenance, per the supply-chain integrity models promoted by SLSA and OWASP guidance on dependency analysis. Third, continuous rescanning: a stored SBOM lets you re-run Grype daily against new CVE data without rebuilding or re-pulling anything—that’s the real payoff of the Syft/Grype decoupling.

Traditional vulnerability scanning tells you what’s broken in your image today. An SBOM program tells you what’s broken in everything you’ve ever shipped, the moment a new CVE drops. Build the inventory first—the rest follows.

Frequently Asked Questions

What is the difference between Syft and Grype?

Syft generates SBOMs by cataloging the packages inside container images, directories, and files. Grype matches that package inventory against known vulnerability databases (NVD, GitHub advisories, OS vendor feeds) and reports findings by severity. Syft answers “what’s in this image?”; Grype answers “which of it is vulnerable?”

Should I output SPDX or CycloneDX?

Both are open standards, and Grype consumes both. CycloneDX carries richer vulnerability and integrity metadata and is the natural fit for security workflows. SPDX is more common for license compliance, government procurement, and communications with legal or customers. Generate both if your consumers differ—it’s one extra flag.

Does Grype need internet access to scan?

Grype needs its vulnerability database, which is cached locally after the first download and refreshed with grype db update. Scanning itself is local. For air-gapped environments, stage the DB cache directory from an internet-connected host and disable auto-updates.

Can Syft scan a running container or only images?

Syft catalogs images, directories, and files—not live container state. If you need to capture what’s actually running (packages installed at runtime, for instance), commit the container to an image or export its filesystem and scan that.

How do I fail a pipeline only on High and Critical findings?

Use grype --fail-on high (or severity-cutoff: high with the Anchore GitHub Action with fail-build: true). Grype exits non-zero if any finding is High or above; Medium, Low, and Negligible won’t break the build.

Hmmnm
Published by Hmmnm

Hands-on cybersecurity tutorials, CVE breakdowns, and guided learning paths — written and lab-tested by the Hmmnm team.

🛡️ Hmmnm also delivers this expertise as a service — security testing, assessment & training.
Keep going — the structured way
This post is one step. The learning paths chain the next ones for you, with progress tracking and no account needed.
Follow a learning path →

Prabhu Kalyan Samal

Application Security Consultant at TCS. Certifications: CompTIA SecurityX, Burp Suite Certified Practitioner, Azure Security Engineer, Azure AI Engineer, Certified Red Team Operator, eWPTX v3, LPT, CompTIA PenTest+, Professional Cloud Security Engineer, SC-900, SC-200, PSPO I, CEH, Oracle Java SE 8, ISP, Six Sigma Green Belt, DELF, AutoCAD. Writing about ethical hacking, security tutorials, and tech education at Hmmnm.