# Syft Cheat Sheet

> **Tool:** Syft
> **Category:** Security & Compliance
> **Verified against:** Syft 1.32.0, flags verified via `syft --help` run locally, 2026-08-29
> **Official docs:** https://github.com/anchore/syft

## What it is and where it fits 🎯

Syft generates a Software Bill of Materials (SBOM) from a container image or filesystem — every package, its
version, and where it came from. This is the "what's actually in this artifact, right now" half of
supply-chain visibility; feed the output into Grype (its sibling tool), Trivy, or a continuously-updating
platform like Dependency-Track to turn an inventory into a vulnerability answer. Syft itself finds nothing
"bad" — it's purely descriptive, which is exactly why it's cheap to run on every single build and archive the
result, rather than treating SBOM generation as an occasional audit activity.

## The generate-once, scan-many pattern

```mermaid
sequenceDiagram
    participant CI as CI Build
    participant Syft as Syft
    participant Store as Artifact storage
    participant Grype as Grype (today)
    participant Grype2 as Grype (6 months from now)

    CI->>Syft: syft scan myapp:latest -o cyclonedx-json
    Syft-->>Store: sbom.cdx.json (archived alongside the release)
    Store->>Grype: grype sbom:sbom.json
    Grype-->>CI: vulnerabilities known TODAY
    Note over Store,Grype2: new CVE published 6 months later
    Store->>Grype2: grype sbom:sbom.json (same file, no re-pull)
    Grype2-->>CI: vulnerabilities known as of TODAY, including the new one
```

The SBOM never goes stale about **what's in the artifact** — only the vulnerability data matched against it
changes over time, and re-matching a stored SBOM is a cheap operation compared to re-pulling and re-scanning
the original image months or years later (which may not even be possible if the image was later deleted from
the registry).

## Installation

```bash
curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin
brew install syft

syft version
```

## Generating an SBOM

```bash
syft scan alpine:latest                                # human-readable summary table (default)
syft scan alpine:latest -o json                         # full Syft-native JSON, all cataloging detail
syft scan alpine:latest -o cyclonedx-json > sbom.cdx.json    # industry-standard, widely consumed downstream
syft scan alpine:latest -o spdx-json > sbom.spdx.json         # the other major standard (Linux Foundation)
syft scan dir:./myproject                                # a filesystem path instead of an image
syft scan registry:myrepo/myimage:tag                    # pull directly from a registry, no daemon required
```

> [!NOTE]
> **CycloneDX vs SPDX** — both are legitimate, widely-adopted SBOM standards; the choice usually comes down
> to what your downstream consumer expects. CycloneDX (OWASP-originated) tends to be favored by
> security-tooling ecosystems (Dependency-Track, most vuln scanners); SPDX (Linux Foundation-originated) is
> more common in license-compliance and legal contexts. Generating both from the same scan is cheap — Syft
> supports multiple `-o` outputs in one invocation.

## Sample output shape

Default table output (illustrative counts — actual packages depend on the scanned image):

```
NAME        VERSION    TYPE
musl        1.2.4-r0   apk
busybox     1.36.1-r15 apk
openssl     3.1.4-r5   apk
```

## Choosing image source explicitly

```bash
syft scan docker:myapp:latest              # local Docker daemon
syft scan podman:myapp:latest
syft scan oci-archive:myapp.tar             # a saved OCI tarball
syft scan file:./go.sum                     # a single manifest file
```

## Scoping and filtering

```bash
syft scan myapp:latest --exclude './vendor/**'
syft scan myapp:latest --scope all-layers          # every layer's contents, not just the final squashed filesystem
syft scan myapp:latest --select-catalogers +sbom    # add/remove/filter which package catalogers run
```

## Chaining into Grype

```bash
syft scan myapp:latest -o json | grype                        # pipe an SBOM straight into a vuln scan, no re-pull needed
syft scan myapp:latest -o cyclonedx-json=sbom.json && grype sbom:sbom.json
```

## Generating an SBOM as an attestation (for cosign)

```bash
syft attest --output cyclonedx-json myapp:latest --key cosign.key > attestation.json
cosign attest --predicate attestation.json --type cyclonedx myapp:latest
```

This is where SBOM generation meets supply-chain signing (see the cosign cheat sheet): a signed attestation
proves not just what's *in* the image, but that the SBOM itself came from your build pipeline and hasn't been
tampered with after the fact.

## Converting between SBOM formats

```bash
syft convert sbom.spdx.json -o cyclonedx-json=sbom.cdx.json
```

## Real-world scenario: archiving an SBOM per release for future-proof compliance

A team facing an upcoming compliance audit (SOC 2, PCI-DSS) needs to answer "what open-source components were
in production version 4.2.0, six months ago" — and the production image from that release may no longer even
exist in the registry.

```bash
# At release time, every release:
syft scan myapp:v4.2.0 -o cyclonedx-json=releases/v4.2.0/sbom.cdx.json
git add releases/v4.2.0/sbom.cdx.json && git commit -m "SBOM for v4.2.0"
```

> [!TIP]
> **Committing the SBOM alongside the release (not just leaving it in a scanner's cache) is what makes this
> genuinely audit-proof.** Six months later, `grype sbom:releases/v4.2.0/sbom.cdx.json` still works
> perfectly even if the container image itself was deleted from the registry to save storage cost — the SBOM
> is the durable record, the image doesn't need to be.

## Real-world scenario: GitHub Actions CI recipe

```yaml
# .github/workflows/syft.yml
name: Generate SBOM
on:
  push:
    tags: ['v*']
jobs:
  sbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t myapp:${{ github.ref_name }} .
      - uses: anchore/sbom-action@v0
        with:
          image: myapp:${{ github.ref_name }}
          format: cyclonedx-json
          output-file: sbom.cdx.json
      - uses: actions/upload-artifact@v4
        with:
          name: sbom-${{ github.ref_name }}
          path: sbom.cdx.json
```

## Shell completion

```bash
syft completion bash | sudo tee /etc/bash_completion.d/syft
```

## Common pitfalls

- **Treating SBOM generation as a one-time audit task** rather than a per-build artifact — the value compounds
  over time specifically because you have one for *every* release, not just the current one.
- **Only generating JSON, not a standard format** — Syft's native JSON is the richest format but the least
  portable; use CycloneDX or SPDX for anything that needs to interoperate with other tooling.
- **Scanning `squashed` scope on an image where a vulnerable package was removed in a later layer** — the
  default `squashed` scope only sees the final filesystem state; `--scope all-layers` is needed to catch a
  package that existed in an intermediate layer (still technically present in the image's history/size) but
  was deleted before the final layer.

## When to reach for something else

Trivy also generates SBOMs (`trivy image --format cyclonedx`) from the same kind of source — a team already
standardized on Trivy for vulnerability scanning may reasonably use its built-in SBOM support instead of
adding Syft as a separate tool. Syft's edge is being purpose-built and maintained specifically for cataloging
depth and format fidelity, which is why Trivy itself uses Syft's cataloging library internally for some of its
own SBOM generation.
