Hamburger Cross Icon
Grype Vulnerability Scanner Collector - Lunar Collector

Grype Vulnerability Scanner Collector

Collector Beta Security

Scans repository dependencies for known CVEs using Grype, Anchore's open-source vulnerability scanner. Writes normalized findings to .sca for use with the SCA policy. No secrets or vendor accounts required.

Add grype to your lunar-config.yml:
uses: github://earthly/lunar-lib/collectors/grype@v1.0.5

What This Integration Collects

This integration includes 4 collectors that gather metadata from your systems.

Collector code

auto

Auto-scans the repository filesystem for dependency vulnerabilities using Grype. Writes normalized findings to .sca with severity counts, CVE IDs, affected packages, and fix versions.

grype vulnerability cve sca dependency scanning auto
Book a demo
Collector cron

rescan

Re-runs the dependency (SCA) scan on a schedule and overwrites .sca so the SCA policy re-evaluates against CVEs published since the commit was first scanned — closing the gap where a branch passes at scan time but a dependency picks up a new CVE later. Runs the same scan as the auto collector (the sibling auto.sh) in the Grype collector image, and stamps .sca.source.integration="cron" (vs "code" for the on-push scan) so it's clear the data came from a scheduled re-scan.

Opt-in scan history: with scan_history_size > 0 the re-scan first snapshots the current .sca (counts + summary + source, including the scan timestamp) into a bounded .sca.history[] array before overwriting .sca, preserving point-in-time scan results for audit. The SCA policy keeps reading the current .sca and behaves identically whether history is on or off. Default 0 keeps today's overwrite-only behavior — no history, no schema change for existing consumers.

sca cve grype dependency scanning cron rescan vulnerability
Book a demo
Collector ci-after-command

cicd

Detects Grype executions in CI pipelines. Captures the command, version, and input arguments for audit and compliance tracking. Routes results by scan target: an image scan (grype <image-ref>) is normalized to .container_scan, while a directory or SBOM scan (grype dir:., grype sbom:...) stays in .sca — so a container scan already running in your pipeline is collected automatically with no extra configuration.

grype ci cicd command detection audit container scan image scan
Book a demo
Collector cron

container-rescan

Re-scans the most recently shipped container image on a schedule. Resolves the image from what the component actually pushed — the most recent docker push (or --push build) in the docker collector's recorded commands (.containers.native.docker.cicd.cmds[]), read via lunar component get-json. That's already-persisted data, so no collector dependency is required. Pulls the image, scans it with Grype, and overwrites .container_scan so a shipped image is re-evaluated against CVEs published after it was built. Override with the container_image input. No code clone needed; runs in the Grype collector image, so the baked-in vulnerability DB is reused.

grype container scan image scan cron rescan cve vulnerability
Book a demo

How Collectors Fit into Lunar

Lunar watches your code and CI/CD systems to collect SDLC data from config files, test results, IaC, deployment configurations, security scans, and more.

Collectors are the automatic data-gathering layer. They extract structured metadata from your repositories and pipelines, feeding it into Lunar's centralized database where guardrails evaluate it to enforce your engineering standards.

Learn How Lunar Works
1
Collectors Gather Data This Integration
Triggered by code changes or CI pipelines, collectors extract metadata from config files, tool outputs, test results, and scans
2
{ } Centralized as JSON
All data merged into each component's unified metadata document
3
Guardrails Enforce Standards
Real-time feedback in PRs and AI workflows

Example Collected Data

This collector writes structured metadata to the Component JSON. Here's an example of the data it produces:

{ } component.json Component JSON
{
  "sca": {
    "source": {
      "tool": "grype",
      "version": "0.74.0",
      "integration": "cron",
      "collected_at": "2026-07-08T02:00:07Z"
    },
    "vulnerabilities": {
      "critical": 0,
      "high": 2,
      "medium": 5,
      "low": 3,
      "total": 10
    },
    "findings": [
      {
        "severity": "high",
        "package": "golang.org/x/net",
        "version": "0.7.0",
        "ecosystem": "go-module",
        "cve": "CVE-2023-44487",
        "title": "HTTP/2 Rapid Reset Attack",
        "fix_version": "0.17.0",
        "fixable": true
      }
    ],
    "summary": {
      "has_critical": false,
      "has_high": true,
      "all_fixable": true
    },
    "history": [
      {
        "source": {"tool": "grype", "version": "0.74.0", "integration": "code", "collected_at": "2026-07-01T02:00:11Z"},
        "vulnerabilities": {"critical": 0, "high": 1, "medium": 5, "low": 3, "total": 9},
        "summary": {"has_critical": false, "has_high": true, "all_fixable": true}
      },
      {
        "source": {"tool": "grype", "version": "0.74.0", "integration": "cron", "collected_at": "2026-07-05T02:00:09Z"},
        "vulnerabilities": {"critical": 0, "high": 2, "medium": 5, "low": 3, "total": 10},
        "summary": {"has_critical": false, "has_high": true, "all_fixable": true}
      }
    ],
    "rescan_count": 2,
    "native": {
      "grype": {
        "cicd": {
          "cmds": [
            {"cmd": "grype dir:. -o json", "version": "0.74.0"}
          ]
        },
        "matches": [
          {
            "vulnerability": {
              "id": "CVE-2023-44487",
              "severity": "High",
              "fix": {"versions": ["0.17.0"], "state": "fixed"},
              "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-44487",
              "cvss": [{"metrics": {"baseScore": 7.5}}]
            },
            "artifact": {
              "name": "golang.org/x/net",
              "version": "0.7.0",
              "type": "go-module",
              "language": "go",
              "purl": "pkg:golang/golang.org/x/net@0.7.0"
            }
          }
        ]
      }
    }
  },
  "container_scan": {
    "source": {
      "tool": "grype",
      "version": "0.74.0",
      "integration": "ci"
    },
    "image": "myregistry.io/app:v1.2.3",
    "os": {
      "family": "alpine",
      "version": "3.19"
    },
    "vulnerabilities": {
      "critical": 0,
      "high": 1,
      "medium": 4,
      "low": 7,
      "total": 12
    },
    "findings": [
      {
        "severity": "high",
        "package": "libssl3",
        "version": "3.1.4-r5",
        "ecosystem": "apk",
        "cve": "CVE-2024-2511",
        "title": "OpenSSL unbounded memory growth in TLSv1.3 session handling",
        "fix_version": "3.1.4-r6",
        "fixable": true
      }
    ],
    "summary": {
      "has_critical": false,
      "has_high": true,
      "all_fixable": true
    },
    "native": {
      "grype": {
        "matches": [
          {
            "vulnerability": {
              "id": "CVE-2024-2511",
              "severity": "High",
              "fix": {"versions": ["3.1.4-r6"], "state": "fixed"}
            },
            "artifact": {
              "name": "libssl3",
              "version": "3.1.4-r5",
              "type": "apk",
              "purl": "pkg:apk/alpine/libssl3@3.1.4-r5"
            }
          }
        ]
      }
    }
  }
}

Configuration

Configure this collector in your lunar-config.yml.

Inputs

Input Required Default Description
scan_history_size Optional 0 Maximum number of prior SCA scans to retain in .sca.history[] on each cron re-scan. Snapshots are ordered oldest-first and the current scan stays in the main .sca fields, so the SCA policy is unaffected. Each entry carries its own source (integration + collected_at), so the release-time scan is identifiable as the integration="code" entry — that's .sca.history[0] when history is enabled from the component's first scan (the intended setup for release-time audit). Enabling it later on a component that has already been re-scanned captures the current .sca as the oldest entry, since the release-time scan was already overwritten. 0 (default) disables history entirely — .sca is overwritten each run exactly as today and no .sca.history is written, so existing consumers see no change. When the cap is reached the oldest entry (index 0) is preserved and the second-oldest is dropped. Only the rescan (cron) sub-collector maintains history; the on-push auto scan ignores this.
max_rescans Optional 0 Stop re-scanning a component after this many scheduled re-scans; 0 (default) means unlimited. Counted independently via .sca.rescan_count (a monotonic tally of completed re-scans), so it stands alone and does not depend on scan_history_size — scan_history_size bounds how many scans you KEEP, max_rescans bounds how many you RUN. Once the limit is reached the cron skips the component, keeping the last scan and its history.
db_auto_update Optional false Download the latest Grype vulnerability database at scan time so scans pick up CVEs published since the collector image was built. Disabled by default for now: enabling it fetches a runtime DB that decompresses to ~1.7GB, which needs the size: large profile the auto/rescan collectors declare — and that only helps on a Hub that honors size (ENG-983), so it's kept off until size-aware Hub builds ship everywhere. Set to "true" on a size-aware Hub to scan the fresh DB; left "false" it scans the lighter image-baked DB, whose freshness tracks the image rebuild cadence.
container_image Required Override the image reference the container-rescan cron scans. By default the cron derives the image from the component's most recent .containers.builds[] entry (populated by the docker collector). Set this to pin a specific image, e.g. "ghcr.io/acme/app:latest".

Secrets

This collector requires the following secrets to be configured in Lunar:

Secret Description
REGISTRY_USERNAME Username for pulling private container images in the container-rescan cron. Optional — public images need no auth. REGISTRY_USER is accepted as an alias.
REGISTRY_USER Alias for REGISTRY_USERNAME (common registry/CI convention). Optional; used only when REGISTRY_USERNAME is unset.
REGISTRY_PASSWORD Password or token for pulling private container images in the container-rescan cron. Optional.

Documentation

View on GitHub

Grype Vulnerability Scanner Collector

Scans source code dependencies for known vulnerabilities using Grype.

Overview

This collector runs Grype — Anchore's open-source vulnerability scanner — against the repository to detect known CVEs in dependencies. It supports the ecosystems Grype covers (Go, Node.js, Python, Java, Rust, Ruby, PHP, .NET, and more) and writes normalized vulnerability data to .sca in the Component JSON, making results immediately consumable by the existing SCA policy. No secrets or vendor accounts are required. By default it scans against the vulnerability database baked into the collector image, so freshness tracks the image rebuild cadence; set db_auto_update: true — on a Hub that honors the size: large profile these collectors declare — to fetch the latest database at scan time and pick up CVEs published since the image was built.

Collected Data

This collector writes to the following Component JSON paths:

Path Type Description
.sca.source object Source metadata (tool name, version, integration method, and collected_at scan timestamp)
.sca.vulnerabilities object Severity counts (critical, high, medium, low, total)
.sca.findings[] array Individual vulnerability findings with CVE, package, fix info
.sca.summary object Summary booleans (has_critical, has_high, all_fixable)
.sca.history[] array (opt-in) Bounded list of prior scan snapshots (source, counts, summary) for point-in-time audit; oldest first. [0] is the oldest retained scan — the release-time (integration="code") scan when history is enabled from the first scan. Absent unless scan_history_size > 0
.sca.rescan_count number (opt-in) Monotonic tally of completed re-scans, used to enforce max_rescans independently of the (capped) .sca.history[] length. Present when scan history or max_rescans is enabled
.sca.native.grype object Raw Grype match output and CI command detection data
.container_scan.source object Source metadata for the container image scan (tool, version, integration)
.container_scan.image string The scanned image reference (e.g. registry/app:tag)
.container_scan.vulnerabilities object Severity counts for the image scan (critical, high, medium, low, total)
.container_scan.findings[] array Individual image findings (OS and application packages)
.container_scan.os object Detected base-image OS family and version
.container_scan.summary object Summary booleans (has_critical, has_high, all_fixable)
.container_scan.native.grype object Raw Grype match output for the image scan

Collectors

This integration provides the following collectors (use include to select a subset):

Collector Hook Type Description
auto code Auto-scans the repository filesystem for dependency vulnerabilities → .sca
cicd ci-after-command Detects Grype executions in CI; routes image scans (grype <image>) to .container_scan and dir/SBOM scans to .sca
rescan cron Re-runs the auto scan on a schedule (daily by default) and overwrites .sca so the SCA policy re-evaluates against newly-published CVEs; optionally snapshots prior scans into .sca.history[] (opt-in via scan_history_size — see Scan history)
container-rescan cron Scans the most recently pushed image (resolved from the docker collector's recorded docker push / --push commands) in the Grype collector image on a schedule and writes .container_scan

Installation

Add to your lunar-config.yml:

collectors:
  - uses: github://earthly/lunar-lib/collectors/grype@main
    on: ["domain:your-domain"]

Zero configuration required. Works with any language Grype supports.

By default this also enables the rescan cron sub-collector, which re-runs the scan daily on each component's default branch and overwrites .sca. To keep the on-push (auto) and CI-detection (cicd) scans but turn the scheduled re-scan off, exclude it:

collectors:
  - uses: github://earthly/lunar-lib/collectors/grype@main
    on: ["domain:your-domain"]
    exclude: [rescan]

Scan history (point-in-time audit)

By default the rescan cron overwrites .sca each run — the SCA policy always sees the latest scan and the previous result is discarded. Because an unchanged artifact accrues newly-disclosed CVEs over time, you may want to keep each scan as a point-in-time record (e.g. "here's what the scan looked like when we shipped this release"). Opt in by keeping a bounded history:

collectors:
  - uses: github://earthly/lunar-lib/collectors/grype@main
    on: ["domain:your-domain"]
    with:
      scan_history_size: "30"   # keep up to 30 prior scans in .sca.history[]
      max_rescans: "0"          # 0 = keep re-scanning forever (default)

With scan_history_size > 0, each re-scan snapshots the current .sca (source + counts + summary, including the collected_at timestamp) into .sca.history[] before overwriting .sca:

  • .sca (unchanged) — the current scan the SCA policy evaluates.
  • .sca.history[0] — the oldest retained scan, preserved even once the cap is reached. When history is enabled from the component's first scan (the intended setup) this is the release-time scan; each entry carries its own source (integration + collected_at), so the release-time scan is the integration: "code" entry — not an assumption about position.
  • .sca.history[1..] — successive prior re-scans, oldest first.

Enabling history on an already-scanned component: if a component has already been re-scanning (overwriting .sca) before you turn on scan_history_size, its release-time scan is already gone, so .sca.history[0] will be the current .sca at enable time (an integration: "cron" entry), not the original code scan. For guaranteed release-time capture, enable history from the component's first scan.

The SCA policy never reads .sca.history, so enabling this changes nothing for policy evaluation — .sca behaves identically whether history is on or off.

Input Default Effect
scan_history_size 0 Max entries kept in .sca.history[]. 0 disables history (today's overwrite-only behavior). At the cap the oldest entry ([0]) is kept and the second-oldest is dropped.
max_rescans 0 Stop re-scanning a component after this many re-scans (0 = unlimited). Counted independently via .sca.rescan_count, so it stands alone — no dependency on scan_history_size.

Both default to off, so existing installs are unchanged. Only the rescan cron maintains history — the on-push auto scan ignores these inputs.

Container image scanning. Beyond source dependencies, this collector scans built container images and writes results to the normalized .container_scan path, consumed by the container-scan policy. Two sub-collectors feed it, neither of which requires the (not-yet-shipped) collector-dependency feature, and — crucially — neither installs Grype (or its ~1.7GB vulnerability DB) in your pipeline:

  • cicd (detect) — if your pipeline already runs grype <image> itself, that scan is captured to .container_scan automatically. A grype dir:/sbom: scan still routes to .sca. No install, no extra config.
  • container-rescan (auto-scan) — a daily cron that resolves the most recently pushed image from the docker collector's recorded commands (.containers.native.docker.cicd.cmds[] — the latest docker push, or a --push build) via lunar component get-json, then pulls and scans it in the Grype collector image, where the vulnerability DB is already baked in. Resolving from pushes rather than .containers.builds[] means a built-but-never-pushed (test/dry-run) image isn't scanned. This is how Lunar scans the shipped image itself with no install on your side, and it re-evaluates that image against CVEs published after it was built. (No push recorded → nothing to scan; the cron skips. Use the container_image input to pin an image explicitly.)

Because the cron reads already-persisted Component JSON (not another collector's output mid-run), it needs no collector-dependency feature. Enable the docker collector alongside this one so its CI docker push commands are recorded:

collectors:
  - uses: github://earthly/lunar-lib/collectors/docker@main
    on: ["domain:your-domain"]
  - uses: github://earthly/lunar-lib/collectors/grype@main
    on: ["domain:your-domain"]

A synchronous on-push auto-scan (scan the image the moment it's built, and only if CI didn't already) needs the component-JSON dependency feature — it follows once that lands.

Private registries: the container-rescan cron pulls the image, so a private registry needs the REGISTRY_USERNAME (or REGISTRY_USER) / REGISTRY_PASSWORD secrets.

Note: The grype collector writes to the same .sca paths as the snyk and trivy collectors. Use one SCA scanner per component, not several, or they will overwrite each other's .sca data.

Re-scan freshness: By default (db_auto_update: false), each cron re-scan uses the DB baked into the collector image, so freshness is tied to the image rebuild cadence — bumping the pinned grype collector version (a newer image ships a newer DB) is what picks up new CVE data. Set db_auto_update: true (on a Hub that honors size: large) to have each re-scan fetch the latest vulnerability database instead, so CVEs published since the last scan surface on the next tick.

Open Source

This collector is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.

View Repository

Ready to Automate Your Standards?

See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 200+ built-in guardrails.

Works with any process
check AI agent rules & prompt files
check Post-mortem action items
check Security & compliance policies
check Testing & quality requirements
Automate Now
Paste your AGENTS.md or manual process doc and get guardrails in minutes
Book a Demo