Vulnerability & SBOM Scanning with Syft and Grype

Vulnerability & SBOM Scanning with Syft and Grype

Modern supply chain security requires more than just scanning for CVEs. A mature container security workflow combines three layers of analysis: SBOM generation to know what's inside your images, vulnerability scanning to find known weaknesses, and malware detection to catch threats that CVE databases don't cover.

This tutorial builds a complete image assessment pipeline using open-source tools:

  1. Syft — Generate Software Bills of Materials (SBOMs) from container images
  2. Grype — Scan SBOMs and images for known vulnerabilities
  3. YARA (via YaraHunter) — Detect malware patterns inside container filesystems
  4. ORAS — Attach scan results as OCI artifacts alongside your images
  5. Cosign — Sign images and SBOMs for supply chain verification

Why SBOMs Matter

An SBOM (Software Bill of Materials) is a complete inventory of every component, library, and dependency inside your software — with versions and license information. Think of it as a nutrition label for your container image.

Without an SBOM, you're flying blind:

  • You can't answer "are we affected?" when a new CVE drops (like Log4Shell)
  • You can't track which images use a compromised dependency
  • You can't prove compliance to auditors or customers
  • You can't automate vulnerability remediation across your fleet

SBOMs turn these questions from multi-day investigations into instant database queries.

Standard SBOM formats:

Format Maintainer Use case
SPDX Linux Foundation Compliance, license tracking, government requirements (required by US EO 14028)
CycloneDX OWASP Security-focused, vulnerability correlation, VEX support
Syft JSON Anchore Native format, maximum detail, integrates with Grype

Vulnerability Scanning

Vulnerability scans identify known security weaknesses by matching package versions against CVE databases. Integrating scans into CI/CD ensures that every image is checked before it reaches production.

Key principles:

  • Scan early — Run scans during the build, not after deployment
  • Fail the pipeline — Set severity thresholds that block builds on HIGH/CRITICAL findings
  • Scan continuously — New CVEs are published daily; images that were clean yesterday may not be today
  • Scan the SBOM, not just the image — SBOM-based scanning is faster and can be done without access to the image layers

Malware Scanning with YARA

CVE databases only cover known vulnerabilities. They don't catch:

  • Trojanized packages with backdoors
  • Cryptominers injected into base images
  • Custom malware targeting your infrastructure
  • Supply chain attacks that modify legitimate packages

YARA fills this gap. It uses pattern-matching rules to identify malware signatures, suspicious strings, and behavioral indicators inside container filesystems. The YaraHunter project from Deepfence packages YARA with a curated CI/CD ruleset for container scanning.


Tool Overview

Syft

Syft is an open-source SBOM generator from Anchore. It scans container images and filesystems to produce detailed component inventories.

Key capabilities:

  • Multiple output formats — SPDX, CycloneDX, and native Syft JSON
  • Comprehensive cataloging — Detects packages across RPM, DEB, APK, Python, Ruby, NPM, Java, Go, and more
  • File metadata — Includes SHA-256/SHA-1 digests for file verification
  • Integration with Grype — Pipe Syft SBOM output directly into Grype for vulnerability analysis
  • Remote license fetching — Retrieves licensing information from package registries
# Generate SBOM in SPDX JSON format
syft <image> -o spdx-json > sbom.spdx.json

# Generate SBOM in CycloneDX format
syft <image> -o cyclonedx-json > sbom.cdx.json

# Generate SBOM from a local directory
syft dir:/path/to/project -o spdx-json

Grype

Grype is Anchore's vulnerability scanner. It matches packages against aggregated vulnerability databases from Alpine SecDB, Debian CVE Tracker, Red Hat OVAL, NVD, and GitHub Security Advisories.

Key capabilities:

  • Broad distribution support — Alpine, CentOS, Debian, Ubuntu, Amazon Linux, RHEL, and more
  • Multiple package formats — RPM, DEB, APK, Python, Ruby, NPM, Java, Go modules
  • Configurable severity thresholds — Fail scans on --fail-on critical for CI/CD gates
  • Output formats — Table, JSON, SARIF (for GitHub Security tab integration)
  • VEX support — Interprets Vulnerability Exploitability eXchange documents to suppress false positives
  • Offline mode — Pre-download the vulnerability database for air-gapped environments
# Scan an image directly
grype <image>

# Scan from an SBOM (faster, no image pull needed)
syft <image> -o json | grype

# Fail on high/critical findings (for CI/CD)
grype <image> --fail-on high

# Output as SARIF for GitHub integration
grype <image> -o sarif > results.sarif

YARA

YARA is the industry standard for malware research and classification. Rules describe patterns — byte sequences, strings, file metadata — that identify malware families.

In the container security context, YARA scans the filesystem layers of an image looking for:

  • Known malware signatures (cryptominers, reverse shells, webshells)
  • Suspicious strings (hardcoded credentials, C2 domains)
  • Packer/obfuscation indicators (UPX headers, base64-encoded payloads)
  • EICAR test patterns (for validating your scanning pipeline)

Lab: Image Assessment Pipeline

This lab builds a complete scan-and-attest workflow: build an image, generate an SBOM, scan for vulnerabilities and malware, then attach all results as OCI artifacts alongside the image.

Prerequisites

  • Docker installed and running
  • Internet access for pulling images and downloading tools

Install Tools

# Install ORAS (OCI Registry As Storage) CLI
VERSION="1.1.0"
curl -LO "https://github.com/oras-project/oras/releases/download/v${VERSION}/oras_${VERSION}_linux_amd64.tar.gz"
mkdir -p oras-install/
tar -zxf oras_${VERSION}_*.tar.gz -C oras-install/
sudo mv oras-install/oras /usr/local/bin/
rm -rf oras_${VERSION}_*.tar.gz oras-install/

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

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

# Verify installations
syft version
grype version
oras version

Step 1: Set Up a Local Registry

A local OCI registry lets you push, scan, and attach artifacts without needing Docker Hub credentials:

# Start a local OCI-compliant registry
docker run -d --restart=always -p "127.0.0.1:5000:5000" --name reg registry:2

Step 2: Build a Test Image

We'll intentionally include the EICAR test file to trigger YARA malware rules — this validates that the scanning pipeline actually catches threats.

# Download EICAR test file (standard AV test pattern)
curl -O https://secure.eicar.org/eicar.com.txt

# Create a Dockerfile with the test file
cat <<'EOF' > Dockerfile
FROM alpine:3.10
WORKDIR /app
COPY ./eicar.com.txt /app/
CMD ["sh"]
EOF

# Build and push to local registry
docker build -t localhost:5000/alpine-test:0.1 .
docker push localhost:5000/alpine-test:0.1

Note: We're using alpine:3.10 intentionally — it's an older version with known CVEs, which makes the vulnerability scan results more interesting.

Step 3: Generate SBOM with Syft

# Generate SBOM in SPDX JSON format
syft localhost:5000/alpine-test:0.1 -o spdx-json > sbom.spdx.json

# View a summary of discovered packages
syft localhost:5000/alpine-test:0.1

The SBOM output lists every package in the image with version, type, and license information. For alpine:3.10, you'll see packages like musl, busybox, apk-tools, zlib, and their exact versions.

Step 4: Vulnerability Scan with Grype

# Scan the image directly
grype localhost:5000/alpine-test:0.1

# Or scan from the SBOM (faster for repeated scans)
grype sbom:sbom.spdx.json

# Generate a report file
grype localhost:5000/alpine-test:0.1 -o json > vuln-scan.json

# For CI/CD: fail on high/critical
grype localhost:5000/alpine-test:0.1 --fail-on high

With alpine:3.10, you'll see multiple CVEs in packages like musl, busybox, and libcrypto. The scan output shows:

  • CVE ID — The vulnerability identifier
  • Severity — Critical, High, Medium, Low, Negligible
  • Package — The affected component
  • Installed version — What's in the image
  • Fixed version — The version that patches the vulnerability

Step 5: Malware Scan with YaraHunter

# Run YaraHunter against the image
docker run -i --rm \
    --name=deepfence-yarahunter \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /tmp:/home/deepfence/output \
    deepfenceio/deepfence_malware_scanner_ce:2.0.0 \
    --image-name localhost:5000/alpine-test:0.1 \
    --output=json > malware-scan.json

# Check results
cat malware-scan.json | python3 -m json.tool | head -50

The EICAR test file should trigger a detection. In a real pipeline, any malware finding should immediately fail the build and trigger an incident response process.

Step 6: Attach Results as OCI Artifacts

OCI artifacts let you store scan results alongside the image in the registry. Anyone pulling the image can also pull its SBOM, vulnerability report, and malware scan results.

# Attach SBOM to the image
oras attach localhost:5000/alpine-test:0.1 \
    sbom.spdx.json --artifact-type application/spdx+json

# Attach vulnerability scan report
oras attach localhost:5000/alpine-test:0.1 \
    vuln-scan.json --artifact-type application/vnd.security.vuln+json

# Attach malware scan results
oras attach localhost:5000/alpine-test:0.1 \
    malware-scan.json --artifact-type application/vnd.security.malware+json

# View the full supply chain artifact tree
oras discover localhost:5000/alpine-test:0.1 -o tree

The oras discover output shows all artifacts attached to the image — your SBOM, vulnerability report, and malware scan are now permanently linked to the image digest.


CI/CD Integration: Makefile

A Makefile ties the entire workflow together. Each target maps to a pipeline stage that can be called from Jenkins, GitLab CI, or GitHub Actions:

REG_URL := localhost:5000
NAME := alpine-test
VERSION := 0.1

.PHONY: build push scan-cve scan-sbom scan-malware attach-artifacts sign verify help

build: ## Build container image
    @docker build . -t $(REG_URL)/$(NAME):$(VERSION)

push: ## Push to registry
    @docker push $(REG_URL)/$(NAME):$(VERSION)

scan-sbom: ## Generate SBOM with Syft
    @syft $(REG_URL)/$(NAME):$(VERSION) -o spdx-json > sbom.spdx.json
    @echo "SBOM saved to sbom.spdx.json"

scan-cve: ## Vulnerability scan with Grype (fails on HIGH/CRITICAL)
    @grype $(REG_URL)/$(NAME):$(VERSION) --fail-on high -o json > vuln-scan.json

scan-malware: ## Malware scan with YaraHunter
    @docker run -i --rm \
        -v /var/run/docker.sock:/var/run/docker.sock \
        -v /tmp:/home/deepfence/output \
        deepfenceio/deepfence_malware_scanner_ce:2.0.0 \
        --image-name $(REG_URL)/$(NAME):$(VERSION) \
        --output=json > malware-scan.json

scan-all: scan-sbom scan-cve scan-malware ## Run all scans

attach-artifacts: ## Attach SBOM and scan results as OCI artifacts
    @oras attach $(REG_URL)/$(NAME):$(VERSION) sbom.spdx.json --artifact-type application/spdx+json
    @oras attach $(REG_URL)/$(NAME):$(VERSION) vuln-scan.json --artifact-type application/vnd.security.vuln+json
    @oras attach $(REG_URL)/$(NAME):$(VERSION) malware-scan.json --artifact-type application/vnd.security.malware+json
    @echo "All artifacts attached"

sign: ## Sign image with Cosign
    @cosign sign --key ~/.sigstore/cosign.key $(REG_URL)/$(NAME):$(VERSION)

verify: ## Verify image signature
    @cosign verify --key cosign.pub $(REG_URL)/$(NAME):$(VERSION)

pipeline: build push scan-all attach-artifacts ## Run full pipeline
    @echo "Pipeline complete"

help: ## Show this help
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

Run the full pipeline with a single command:

make pipeline

Or run individual stages:

make build
make push
make scan-all
make attach-artifacts

Continuous Scanning

Build-time scanning is necessary but not sufficient. New CVEs are published daily — an image that was clean at build time may have critical vulnerabilities discovered weeks later.

Strategies for continuous scanning:

  • Scheduled registry scans — Run grype against all images in your registry on a daily cron job
  • Admission control — Use Kubernetes admission controllers to block deployment of images older than N days or with unscanned SBOMs
  • Runtime SBOM correlation — Match running container SBOMs against live CVE feeds to get real-time alerts
  • SBOM-based impact analysis — When a new CVE drops, query your SBOM database to instantly find all affected images
# Example: scan all images in a local registry
for tag in $(oras repo tags localhost:5000/alpine-test); do
    echo "Scanning alpine-test:$tag..."
    grype localhost:5000/alpine-test:$tag --fail-on critical || echo "CRITICAL CVEs found in $tag"
done

Summary

A complete container security assessment combines three layers:

Layer Tool What it catches
Inventory Syft All packages, libraries, and dependencies with versions
Vulnerabilities Grype Known CVEs matched against vulnerability databases
Malware YARA/YaraHunter Malware signatures, suspicious patterns, backdoors

By generating SBOMs, running vulnerability and malware scans, and attaching results as OCI artifacts, you create a verifiable chain of evidence for every image in your supply chain. This multi-layered approach is essential for maintaining security in modern container environments.


Next Steps

  • Set up Cosign signing — Add cryptographic signatures to your images and SBOMs for tamper detection
  • Integrate with GitHub Actions — Use Grype's SARIF output to surface vulnerabilities in the GitHub Security tab
  • Deploy admission control — Block unscanned or unsigned images from running in Kubernetes with Gatekeeper policies
  • Explore the YARA malware scanning tutorial — The Docker and YARA tutorial goes deeper into writing custom YARA rules
  • Build a vulnerability dashboard — Aggregate Grype JSON output into Elasticsearch or Grafana for fleet-wide visibility