# Grype Cheat Sheet

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

## What it is and where it fits 🎯

Grype is Anchore's vulnerability scanner for container images, filesystems, and SBOMs — narrower in scope
than Trivy (no built-in IaC scanning, no secrets detection), but that narrowness is the point: it does one job
— match a package inventory against known vulnerabilities — and does it fast, with a design that pairs
naturally with its sibling tool Syft. The idiomatic Anchore workflow is "generate once, scan many times": Syft
builds the SBOM, Grype (or Trivy, or a platform like Dependency-Track) consumes it. If a team already runs
Trivy for everything, adding Grype purely for image scanning is redundant; Grype earns its place when a team
wants Syft's SBOM generation and prefers Anchore's own matching engine over Trivy's for that half of the job.

## How Grype resolves a scan target ⚙️

```mermaid
flowchart TD
    A["grype &lt;target&gt;"] --> B{"What kind of target?"}
    B -->|"image reference"| C["Pull from local Docker/Podman daemon"]
    B -->|"registry: prefix"| D["Pull directly from registry, no daemon"]
    B -->|"dir: / file:"| E["Read filesystem path directly"]
    B -->|"sbom: / piped JSON"| F["Reuse an existing Syft SBOM — skip re-cataloging entirely"]
    C & D & E --> G["Syft's cataloging engine runs internally to build the package list"]
    F --> H["Match against the local vuln DB"]
    G --> H
    H --> I["Report: table / json / cyclonedx-json / sarif..."]

    classDef info fill:#e5f0fa,stroke:#1d6fb8,color:#10161c
    classDef ok fill:#e5f5ea,stroke:#1f8a4c,color:#10161c
    class F,H ok
    class C,D,E info
```

The `sbom:` path is the fast one — Grype embeds the same cataloging library Syft uses, so scanning an image
directly re-does that cataloging work internally; feeding it an already-generated Syft SBOM skips straight to
vulnerability matching.

## Installation

```bash
curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin
# add -v to also verify the downloaded binary's signature with cosign
brew install grype

grype version
```

## Scanning an image

```bash
grype yourrepo/yourimage:tag              # defaults to pulling from the local Docker daemon
grype docker:yourrepo/yourimage:tag       # explicit Docker daemon source
grype registry:yourrepo/yourimage:tag     # pull directly from a registry — no container runtime required
grype podman:yourrepo/yourimage:tag
grype docker-archive:path/to/image.tar    # a `docker save` tarball
```

## Scanning a filesystem or a purl/CPE directly

```bash
grype dir:path/to/yourproject
grype file:path/to/yourfile
grype pkg:npm/lodash@4.17.15                                # a single package URL, no source to scan
grype cpe:2.3:a:openssl:openssl:3.0.14:*:*:*:*:*:*:*
```

## Piping Syft SBOMs straight in

```bash
syft yourimage:tag -o json | grype
grype sbom:path/to/syft.json                                # from a saved Syft SBOM
```

> [!TIP]
> **Generate the SBOM once at build time, store it as a build artifact, and scan it repeatedly afterward**
> rather than re-pulling and re-scanning the image every time a new CVE feed lands. This is the same
> "instant search across everything we've ever built" pattern documented on the Syft and Trivy SBOM pages —
> a stored SBOM never goes stale about what's *in* the artifact, only the vulnerability data matched against
> it does, and that's a cheap re-match, not a re-scan.

## Gating CI on severity

```bash
grype myapp:latest --fail-on high            # exit 1 if any HIGH-or-above vuln is found (0 by default!)
```

> [!WARNING]
> Like Trivy, Grype's default exit code is **0 regardless of findings** — `--fail-on` is what turns it into a
> real CI gate. Confirmed the single most common cause of "the scanner ran but never blocked anything" reports
> across every vulnerability scanner in this series (Trivy, Grype both share this default).

## Filtering and output

```bash
grype myapp:latest --only-fixed                              # hide vulns with no available fix
grype myapp:latest -o json > results.json
grype myapp:latest -o table                                  # default, human-readable
grype myapp:latest -o cyclonedx-json > results.cdx.json
grype myapp:latest --scope all-layers                        # inspect every image layer, not just the squashed result
grype myapp:latest -s low --exclude '**/test/**'
grype myapp:latest --by-cve                                   # orient results by CVE id instead of the internal vuln id — easier to cross-reference against an advisory feed
```

Sample table output (illustrative shape — actual CVEs, counts, and column widths depend entirely on the image
scanned and the vuln DB's state that day):

```
NAME      INSTALLED  FIXED-IN  TYPE  VULNERABILITY   SEVERITY
openssl   1.1.1b-r1  1.1.1k-r0 apk   CVE-2021-3711   Critical
libcurl   7.64.0-r2  7.64.0-r3 apk   CVE-2020-8231   High
```

## Explaining a finding 🔍

```bash
grype myapp:latest -o json > results.json
grype explain --id CVE-2024-12345 -f results.json    # why did this CVE match, what's the path to the vulnerable package
```

This answers the question a raw finding list never does on its own: "my base image is `alpine:3.18` — why is
Grype reporting a CVE against a package I never installed?" `explain` walks the match back to the specific
layer/package/version that triggered it.

## Real-world scenario: comparing Syft+Grype against Trivy on the same image

A team migrating from Trivy to the Syft/Grype pairing (or vice versa) should expect some non-overlap — the
two use different vulnerability data sources and matching logic, so a side-by-side run on the same image is
worth doing before fully committing:

```bash
trivy image --format json --output trivy-results.json myapp:latest
syft myapp:latest -o json | grype -o json > grype-results.json
# diff the CVE ID lists from each report — expect partial, not total, overlap
```

> [!NOTE]
> Neither tool is strictly a superset of the other. A production security pipeline that genuinely can't
> tolerate a missed finding sometimes runs both and unions the results, accepting the operational cost of
> maintaining two scanners for the coverage gain.

## Real-world scenario: GitHub Actions CI recipe

```yaml
# .github/workflows/grype.yml
name: Grype Image Scan
on: [pull_request]
jobs:
  grype:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      - uses: anchore/scan-action@v4
        id: scan
        with:
          image: myapp:${{ github.sha }}
          fail-build: true
          severity-cutoff: high
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: ${{ steps.scan.outputs.sarif }}
```

## Shell completion

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

## Common pitfalls

- **Assuming `--fail-on` is the default behavior** — it isn't; a bare `grype myapp:latest` in CI with no exit
  gating never fails a build regardless of what it finds.
- **Scanning `latest` and comparing results day-to-day** — a mutable tag drifts; pin to a digest for anything
  you intend to track as a trend.
- **Expecting identical results to Trivy** — different data sources produce different (overlapping, not
  identical) findings; don't treat either tool's output as the sole ground truth.

## Exit codes

`0` clean (or findings exist but `--fail-on` not set) · `1` findings at/above the `--fail-on` severity
threshold.

## When to reach for something else

For IaC misconfigurations, secrets, or a single tool covering everything, Trivy is the broader option — see
its two cheat-sheet pages in this section. For SBOM generation specifically, pair Grype with Syft rather than
reaching for Trivy's built-in SBOM support, if Anchore's matching engine is the one the team has standardized
on.
