supply chain security

Generating and Analyzing SBOMs

Produce and ingest CycloneDX and SPDX SBOMs and correlate them to vulnerability intelligence.

cyclonedxdevsecopsgrypesbomspdxsupply-chain-securitysyftvulnerability-management
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Authorized Use Only: Generate and scan SBOMs only for software and images you own or are authorized to assess. Treat SBOMs as sensitive inventory data — they reveal your dependency attack surface.

Overview

A Software Bill of Materials (SBOM) is a formal, machine-readable inventory of every component, library, and dependency in a piece of software — the supply-chain equivalent of an ingredients label. SBOMs are central to defending against supply-chain compromise (CISA's SBOM initiative, US Executive Order 14028) because you cannot patch what you cannot see. The two dominant SBOM standards are:

  • CycloneDX — an OWASP standard optimized for security use cases (vulnerabilities, VEX, dependency relationships).
  • SPDX — a Linux Foundation / ISO standard (ISO/IEC 5962) strong on licensing and provenance.

The reference open-source toolchain is from Anchore:

  • Syft generates SBOMs (CycloneDX, SPDX, or its native format) from container images and filesystems.
  • Grype matches an SBOM (or image) against vulnerability databases to find CVEs.
  • Cosign (Sigstore) signs SBOMs and attaches them to images as signed attestations for tamper-evident provenance.

This skill covers producing standards-compliant SBOMs, correlating them with vulnerability intelligence, and embedding the workflow into CI/CD.

When to Use

  • Establishing and maintaining a component inventory for applications and container images.
  • Continuously detecting known vulnerabilities (including newly disclosed CVEs against existing artifacts).
  • Satisfying procurement/regulatory SBOM requirements (CISA, EO 14028).
  • Producing signed SBOM attestations for downstream supply-chain trust.

Prerequisites

  • Install Syft and Grype (official install scripts):
    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
  • Install Cosign for signing/attestation:
    # via Go, or download a release from https://github.com/sigstore/cosign/releases
    go install github.com/sigstore/cosign/v2/cmd/cosign@latest
  • Access to the target images/source and (for signing) a registry plus keys or keyless OIDC.

Objectives

  • Generate CycloneDX and SPDX SBOMs from images and directories.
  • Scan SBOMs and images for vulnerabilities with Grype.
  • Gate CI/CD builds on severity thresholds.
  • Sign and attach SBOM attestations with Cosign and verify them.

MITRE ATT&CK Mapping

ID Official Technique Name Relevance to this skill
T1195.001 Supply Chain Compromise: Compromise Software Dependencies and Development Tools SBOM generation and vulnerability correlation expose compromised or vulnerable dependencies — the attack surface adversaries abuse under this technique.

This is a defensive supply-chain skill; the mapping reflects the adversary technique it is designed to detect and mitigate.

Workflow

1. Generate a CycloneDX SBOM from a container image

-o <format> selects output; cyclonedx-json is security-oriented.

syft alpine:latest -o cyclonedx-json=alpine.cdx.json

2. Generate an SPDX SBOM from a source directory

Use the dir: source to inventory a checked-out repository; spdx-json for the SPDX standard.

syft dir:. -o spdx-json=app.spdx.json

3. Emit multiple formats at once

Produce both standards in a single pass for different consumers.

syft myorg/app:1.4.2 \
  -o cyclonedx-json=app.cdx.json \
  -o spdx-json=app.spdx.json \
  -o table

4. Scan the SBOM for vulnerabilities with Grype

Decoupling generation from scanning lets you re-scan stored SBOMs as new CVEs land — without rebuilding.

# Scan an existing SBOM
grype sbom:app.cdx.json -o table
 
# JSON report for automation
grype sbom:app.cdx.json -o json > app.vulns.json

You can also scan an image directly (Grype generates the SBOM internally):

grype myorg/app:1.4.2 -o table

5. Gate CI/CD on severity

--fail-on exits non-zero at or above a severity, failing the pipeline.

grype sbom:app.cdx.json --fail-on high

Filter out unfixable noise with a .grype.yaml policy (only-fixed: true) or --only-fixed:

grype sbom:app.cdx.json --only-fixed --fail-on critical

6. Sign and attach the SBOM as an attestation

Cosign records the SBOM as a signed, in-toto attestation alongside the image in the registry.

# Key-based signing
cosign attest --key cosign.key \
  --predicate app.spdx.json \
  --type spdxjson \
  myorg/app:1.4.2
 
# Keyless (Sigstore OIDC / Fulcio + Rekor)
COSIGN_EXPERIMENTAL=1 cosign attest \
  --predicate app.cdx.json \
  --type cyclonedx \
  myorg/app:1.4.2

7. Verify the attestation downstream

Consumers verify provenance before trusting an image.

cosign verify-attestation --key cosign.pub --type spdxjson myorg/app:1.4.2

8. Retrieve and re-scan attached SBOMs

Pull the attested SBOM from the registry and re-run Grype as part of continuous monitoring.

cosign download attestation myorg/app:1.4.2 \
  | jq -r '.payload' | base64 -d | jq '.predicate' > pulled.spdx.json
grype sbom:pulled.spdx.json -o table

9. Correlate to vulnerability intelligence

Feed Grype JSON into your vulnerability management workflow: deduplicate by CVE, enrich with EPSS/KEV for prioritization, and track remediation SLAs. Re-scan stored SBOMs on each Grype DB update to catch newly disclosed CVEs in unchanged artifacts.

Tools and Resources

Tool Purpose Link
Syft SBOM generation https://github.com/anchore/syft
Grype Vulnerability scanning of SBOMs/images https://github.com/anchore/grype
Cosign SBOM signing/attestation https://github.com/sigstore/cosign
CycloneDX Security-focused SBOM standard https://cyclonedx.org/
SPDX ISO SBOM standard https://spdx.dev/
CISA SBOM Guidance and minimum elements https://www.cisa.gov/sbom

Format Comparison

Aspect CycloneDX SPDX
Steward OWASP Linux Foundation / ISO 5962
Strength Security, VEX, vulnerabilities Licensing, provenance
Common syft -o values cyclonedx-json, cyclonedx-xml spdx-json, spdx (tag-value)

Validation Criteria

  • CycloneDX SBOM generated from the target image
  • SPDX SBOM generated from source where required
  • SBOM scanned with Grype producing a CVE report
  • CI/CD gated with --fail-on at an agreed severity
  • SBOM signed and attached as an attestation with Cosign
  • Attestation verified downstream
  • Stored SBOMs re-scanned on Grype DB updates
  • Findings correlated/prioritized (EPSS/KEV) and tracked to remediation
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.3 KB

SBOM Toolchain Command Reference

Syft (SBOM generation)

Source prefixes: <image> (default = container image), dir:<path>, file:<path>, registry:<image>, docker:<image>, oci-archive:<path>.

Flag / form Purpose
-o <format>[=<file>] Output format and optional file
--scope <squashed|all-layers> Layer scope for images
--exclude <glob> Exclude paths
syft <src> -o table Human-readable summary

Common -o formats: cyclonedx-json, cyclonedx-xml, spdx-json, spdx (tag-value), syft-json, table.

syft alpine:latest -o cyclonedx-json=alpine.cdx.json
syft dir:. -o spdx-json=app.spdx.json
syft myorg/app:1.4.2 -o cyclonedx-json=app.cdx.json -o spdx-json=app.spdx.json -o table

Grype (vulnerability scanning)

Source prefixes: sbom:<file>, <image>, dir:<path>, registry:<image>.

Flag Purpose
-o <format> table, json, cyclonedx, sarif
--fail-on <severity> Exit non-zero at/above severity (low|medium|high|critical)
--only-fixed Report only vulns with a fix available
--add-cpes-if-none Improve matching for SBOMs lacking CPEs
db update Update the vulnerability database
grype sbom:app.cdx.json -o table
grype sbom:app.cdx.json -o json > app.vulns.json
grype sbom:app.cdx.json --only-fixed --fail-on critical
grype myorg/app:1.4.2 -o table
grype db update

Cosign (signing / attestation)

Command Purpose
cosign attest --key <key> --predicate <sbom> --type <type> <image> Attach signed SBOM attestation
cosign verify-attestation --key <pub> --type <type> <image> Verify attestation
cosign download attestation <image> Retrieve attached attestation
cosign generate-key-pair Create signing keys

--type values: spdxjson, cyclonedx, slsaprovenance, or a custom URI. Keyless mode: set COSIGN_EXPERIMENTAL=1 and omit --key (uses Fulcio/Rekor).

cosign attest --key cosign.key --predicate app.spdx.json --type spdxjson myorg/app:1.4.2
cosign verify-attestation --key cosign.pub --type spdxjson myorg/app:1.4.2
cosign download attestation myorg/app:1.4.2

Policy file (.grype.yaml)

only-fixed: true
fail-on-severity: high
ignore:
  - vulnerability: CVE-2024-0000   # documented, risk-accepted
standards.md1.4 KB

Standards and Framework Mapping — Generating and Analyzing SBOMs

NIST Cybersecurity Framework 2.0

ID Name Rationale
ID.AM-08 Systems, hardware, software, services, and data are managed throughout their life cycles SBOMs are the authoritative software-component inventory that underpins lifecycle asset management and supply-chain risk visibility.

MITRE ATT&CK

ID Name Rationale
T1195.001 Supply Chain Compromise: Compromise Software Dependencies and Development Tools SBOM generation plus vulnerability correlation surfaces vulnerable/compromised dependencies, directly countering this technique.

SBOM Standards and Authorities

Standard / Authority Role
CycloneDX (OWASP) Security-focused SBOM format (VEX, vulnerabilities)
SPDX (ISO/IEC 5962) Licensing/provenance-focused SBOM format
CISA SBOM Minimum Elements Baseline required SBOM fields
US Executive Order 14028 Mandates SBOMs for software sold to the US government
NTIA "Framing Software Component Transparency" Foundational SBOM guidance

Supporting References

Scripts 1

agent.py5.3 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
SBOM generation + vulnerability correlation helper.

Wraps Syft (SBOM generation), Grype (vulnerability scanning), and Cosign
(attestation) with real flags. Can run the full pipeline (generate -> scan ->
optionally attest) and summarize Grype JSON by severity for CI gating.

References:
  https://github.com/anchore/syft
  https://github.com/anchore/grype
  https://github.com/sigstore/cosign
"""
import argparse
import json
import shutil
import subprocess
import sys
from collections import Counter

SEVERITY_ORDER = ["negligible", "low", "medium", "high", "critical"]


def require(tool):
    path = shutil.which(tool)
    if not path:
        print(f"[!] '{tool}' not found on PATH", file=sys.stderr)
    return path


def run(cmd, dry_run, capture=False):
    print("[*] " + " ".join(cmd), file=sys.stderr)
    if dry_run:
        return 0, ""
    try:
        if capture:
            proc = subprocess.run(cmd, check=False, text=True, capture_output=True)
            if proc.returncode != 0 and proc.stderr:
                print(proc.stderr, file=sys.stderr)
            return proc.returncode, proc.stdout
        return subprocess.run(cmd, check=False).returncode, ""
    except FileNotFoundError:
        print(f"[!] command not found: {cmd[0]}", file=sys.stderr)
        return 127, ""


def syft_generate(source, fmt, outfile, dry_run):
    syft = require("syft") or "syft"
    cmd = [syft, source, "-o", f"{fmt}={outfile}"]
    rc, _ = run(cmd, dry_run)
    return rc


def grype_scan(sbom_path, out_json, fail_on=None, only_fixed=False, dry_run=False):
    grype = require("grype") or "grype"
    cmd = [grype, f"sbom:{sbom_path}", "-o", "json"]
    if only_fixed:
        cmd.append("--only-fixed")
    if fail_on:
        cmd += ["--fail-on", fail_on]
    rc, out = run(cmd, dry_run, capture=True)
    if out and not dry_run:
        with open(out_json, "w", encoding="utf-8") as fh:
            fh.write(out)
    return rc, out


def summarize(grype_json_text):
    """Count vulnerabilities by severity from Grype JSON output."""
    try:
        data = json.loads(grype_json_text)
    except (json.JSONDecodeError, TypeError):
        print("[!] could not parse Grype JSON", file=sys.stderr)
        return Counter()
    counts = Counter()
    for match in data.get("matches", []):
        sev = (match.get("vulnerability", {})
                    .get("severity", "Unknown")).lower()
        counts[sev] += 1
    return counts


def print_summary(counts):
    total = sum(counts.values())
    print(f"[+] Total vulnerabilities: {total}")
    for sev in reversed(SEVERITY_ORDER):
        if counts.get(sev):
            print(f"    {sev:>10}: {counts[sev]}")
    for sev, n in counts.items():
        if sev not in SEVERITY_ORDER:
            print(f"    {sev:>10}: {n}")


def cosign_attest(image, predicate, attest_type, key, dry_run):
    cosign = require("cosign") or "cosign"
    cmd = [cosign, "attest", "--predicate", predicate, "--type", attest_type, image]
    if key:
        cmd[2:2] = ["--key", key]  # insert after 'attest'
    rc, _ = run(cmd, dry_run)
    return rc


def main():
    p = argparse.ArgumentParser(description="SBOM generate/scan/attest helper")
    p.add_argument("--dry-run", action="store_true")
    sub = p.add_subparsers(dest="cmd", required=True)

    g = sub.add_parser("generate", help="Generate an SBOM with Syft")
    g.add_argument("--source", required=True, help="image, dir:., file:..., etc.")
    g.add_argument("--format", default="cyclonedx-json")
    g.add_argument("--out", required=True)

    s = sub.add_parser("scan", help="Scan an SBOM with Grype")
    s.add_argument("--sbom", required=True)
    s.add_argument("--out", default="vulns.json")
    s.add_argument("--fail-on")
    s.add_argument("--only-fixed", action="store_true")

    pl = sub.add_parser("pipeline", help="generate -> scan -> summarize (+attest)")
    pl.add_argument("--source", required=True)
    pl.add_argument("--format", default="cyclonedx-json")
    pl.add_argument("--sbom-out", default="sbom.json")
    pl.add_argument("--vulns-out", default="vulns.json")
    pl.add_argument("--fail-on")
    pl.add_argument("--only-fixed", action="store_true")
    pl.add_argument("--attest-image", help="If set, attest the SBOM to this image")
    pl.add_argument("--attest-type", default="cyclonedx")
    pl.add_argument("--cosign-key")

    args = p.parse_args()

    if args.cmd == "generate":
        return syft_generate(args.source, args.format, args.out, args.dry_run)

    if args.cmd == "scan":
        rc, out = grype_scan(args.sbom, args.out, args.fail_on,
                             args.only_fixed, args.dry_run)
        if out:
            print_summary(summarize(out))
        return rc

    if args.cmd == "pipeline":
        rc = syft_generate(args.source, args.format, args.sbom_out, args.dry_run)
        if rc not in (0,) and not args.dry_run:
            print("[!] SBOM generation failed", file=sys.stderr)
            return rc
        scan_rc, out = grype_scan(args.sbom_out, args.vulns_out, args.fail_on,
                                  args.only_fixed, args.dry_run)
        if out:
            print_summary(summarize(out))
        if args.attest_image:
            cosign_attest(args.attest_image, args.sbom_out, args.attest_type,
                          args.cosign_key, args.dry_run)
        return scan_rc
    return 2


if __name__ == "__main__":
    sys.exit(main())
Keep exploring