Hamburger Cross Icon
Container Scan Guardrails - Lunar Policy for Security And Compliance

Container Scan Guardrails

Policy Stable Security And Compliance

Enforces container image vulnerability scanning standards. Ensures container scanners run and validates vulnerability thresholds for container security.

Add container-scan to your lunar-config.yml:
uses: github://earthly/lunar-lib/policies/container-scan@v1.0.5

Included Guardrails

This policy includes 3 guardrails that enforce standards for your security and compliance.

Guardrail

executed

Verifies that container scanning was executed on the component. Fails if no scanner has written to .container_scan.

container security scanning compliance docker image
View Guardrail
Guardrail

max-severity

Ensures no findings at or above the configured severity threshold. Configure min_severity to set the threshold (critical, high, medium, low).

container critical high vulnerabilities security docker
View Guardrail
Guardrail

max-total

Ensures total container vulnerabilities are under a configurable threshold.

container threshold vulnerabilities docker
View Guardrail

How Guardrails Fit into Lunar

Lunar guardrails define your engineering standards as code. They evaluate data collected by integrations and produce pass/fail checks with actionable feedback.

Policies support gradual enforcement—from silent scoring to blocking PRs or deployments—letting you roll out standards at your own pace without disrupting existing workflows.

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

Required Integrations

This policy evaluates data gathered by one or more of the following integration(s). Make sure to enable them in your lunar-config.yml.

Configuration

Configure this policy in your lunar-config.yml.

Inputs

Input Required Default Description
min_severity Optional high Minimum severity to fail on (critical, high, medium, low)
ignore_unfixable Optional false Only fail max-severity on findings that have a fix available. Unfixable findings are still reported in the component JSON.
max_total_threshold Required Maximum total findings allowed (must be configured)

Documentation

View on GitHub

Container Scan Guardrails

Enforces container image vulnerability scanning standards for container security.

Overview

This policy validates that container scanning is configured and enforces vulnerability thresholds for container images. It works with any container scanner that writes to the normalized .container_scan path in the Component JSON (Trivy, Grype, Snyk Container, etc.).

Policies

This plugin provides the following policies (use include to select a subset):

Policy Description Failure Meaning
executed Verifies container scanning ran No scanner has written to .container_scan
max-severity No findings at or above severity threshold Findings found at configured severity or higher
max-total Total vulnerabilities under threshold Total count exceeds configured limit

Required Data

This policy reads from the following Component JSON paths:

Path Type Provided By
.container_scan object Any container scanner collector (Trivy, Grype, etc.)
.container_scan.vulnerabilities.critical number Container scanner collector
.container_scan.vulnerabilities.high number Container scanner collector
.container_scan.vulnerabilities.medium number Container scanner collector
.container_scan.vulnerabilities.low number Container scanner collector
.container_scan.vulnerabilities.total number Container scanner collector
.container_scan.summary.has_critical boolean Container scanner collector (preferred)
.container_scan.summary.has_high boolean Container scanner collector (preferred)
.container_scan.summary.has_medium boolean Container scanner collector (preferred)
.container_scan.summary.has_low boolean Container scanner collector (preferred)
.containers.native.docker.cicd.cmds[] array docker collector, plus any CI step that records a pushed image — the pushed refs decide whether max-severity / max-total apply
.container_scan.findings[] array Container scanner collector — names the offending packages/CVEs in the max-severity failure message and drives ignore_unfixable (cve, severity, package, fix_version, fixable)

Note: If collectors don't yet write vulnerability counts, the max-severity and max-total checks will fail. Use include: [executed] to only verify the scanner ran until collectors are enhanced.

Applicability. max-severity and max-total skip a component with no .containers, and also skip a commit whose .containers record carries no pushed image ref — a CI tracer records docker info / docker ps there, so the object alone does not mean an image shipped. With a pushed ref and no .container_scan they fail: something shipped that nothing scanned. The pushed-ref resolution matches the one the Trivy and Grype container sub-collectors use to choose images, so these checks are applicable exactly when a scanner had a target. executed does not consult it — include it to require a scan whatever the CI record shows.

Installation

Add to your lunar-config.yml:

policies:
  - uses: github://earthly/lunar-lib/policies/container-scan@main
    on: ["domain:your-domain"]
    enforcement: report-pr
    # include: [executed, max-severity]  # Only run specific checks
    with:
      min_severity: "high"        # Fail on critical and high findings
      max_total_threshold: "10"   # Fail if more than 10 total findings
      # ignore_unfixable: "true"  # Optional: only fail on findings that have a fix

Only failing on fixable findings

By default max-severity fails on every finding at or above min_severity, whether or not a fix exists. That is the right default, but base images routinely carry criticals that the upstream distro has not fixed — so a release gate on critical can end up permanently closed on findings no change to your own image can clear. Set ignore_unfixable: "true" to narrow the failure to findings that carry an upgrade target, which is what makes a block-pr-and-release gate practical:

    enforcement: block-pr-and-release
    with:
      min_severity: "critical"
      ignore_unfixable: "true"

Unfixable findings are still collected and still visible in the Component JSON and the dashboard — the option changes what the check gates on, never what is recorded. It can only turn a failure into a pass: it is applied after the threshold has already been crossed, so it never creates a failure the default would not have raised.

Two behaviours worth knowing:

  • A finding counts as fixable when the scanner reported a fix version. Trivy and Grype can both scan the same image; where they disagree, the one that found a fix wins — a fix that exists is actionable.
  • If the scanner reported only summary counts and no .container_scan.findings[], fixability cannot be evaluated per finding, so the check fails as it would with the option off and says so in the failure message. .container_scan.summary.all_fixable is not a substitute: it is a single boolean across all severities, so it cannot answer whether the in-scope findings are fixable.

Examples

Passing Example

{
  "container_scan": {
    "source": { "tool": "trivy", "integration": "cicd" },
    "vulnerabilities": { "critical": 0, "high": 0, "medium": 5, "total": 12 },
    "summary": { "has_critical": false, "has_high": false }
  }
}

Failing Example

{
  "container_scan": {
    "source": { "tool": "trivy", "integration": "cicd" },
    "vulnerabilities": { "critical": 3, "high": 8, "medium": 15, "total": 40 },
    "findings": [
      { "severity": "critical", "package": "openssl", "cve": "CVE-2026-1234", "fix_version": "3.0.14" },
      { "severity": "high", "package": "libcurl", "cve": "CVE-2026-5678", "fix_version": null }
    ],
    "summary": { "has_critical": true, "has_high": true }
  }
}

Failure messages:

  • executed: "No container scan results at this commit, though it pushed 2 image(s) — either no scanner is configured for this component, or a configured one recorded nothing here.", followed by one not scanned: <image> assertion per pushed image. max-severity and max-total say the same when an image shipped and nothing scanned it; with no pushed image they skip instead.
  • max-severity: fails with a headline assertion plus one assertion per offending package/CVE (most severe first), the same format the sca policy uses — no policy-side cap; the hub truncates the display. When the scanner emits per-finding detail it renders as a nested list under the check:
    ❌ max-severity
      * Critical container vulnerabilities detected
      * critical: openssl — CVE-2026-1234 (fix: 3.0.14)
      * high: libcurl — CVE-2026-5678 (no fix available)
    
    A summary-only scanner (no .container_scan.findings[]) falls back to the headline alone (e.g. Critical container vulnerabilities detected).
  • max-total: "Total container vulnerability findings (40) exceeds threshold (10)"

Remediation

When this policy fails, you can resolve it by:

  1. executed failure: Configure a container scanner (Trivy, Grype, Snyk Container) in your CI pipeline. If one is already configured, check its run for this commit — a scanner that ran but recorded nothing leaves the same empty .container_scan.
  2. max-severity failure: Review and remediate flagged vulnerabilities by updating base images or using vulnerability suppression for accepted risks.
  3. max-total failure: Reduce total vulnerability count by updating base images and dependencies.

Open Source

This policy 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