Hamburger Cross Icon
SCA Guardrails - Lunar Policy for Security And Compliance

SCA Guardrails

Policy Stable Security And Compliance

Enforces Software Composition Analysis scanning standards. Ensures SCA scanners run and validates vulnerability thresholds for dependency security.

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

Included Guardrails

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

Guardrail

executed

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

sca security scanning compliance dependencies
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).

sca critical high vulnerabilities security dependencies webhook alert
View Guardrail
Guardrail

max-total

Ensures total vulnerabilities are under a configurable threshold.

sca threshold vulnerabilities dependencies
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

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)
alert_url Required Optional webhook URL. When set, a failing max-severity check also POSTs a JSON payload describing the findings at or above min_severity. Leave empty to disable alerting (default).
alert_timeout_sec Optional 2 Webhook POST timeout in seconds. Best-effort — a slow or dead endpoint never changes the check result.

Documentation

View on GitHub

SCA Guardrails

Enforces Software Composition Analysis (SCA) scanning standards for dependency security.

Overview

This policy validates that SCA scanning is configured and enforces vulnerability thresholds for dependencies. It works with any SCA scanner that writes to the normalized .sca path in the Component JSON (Snyk, Semgrep Supply Chain, Dependabot, Grype, etc.).

Policies

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

Policy Description Failure Meaning
executed Verifies SCA scanning ran No scanner has written to .sca
max-severity No findings at or above severity threshold (optionally fires a webhook on failure) 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
.sca object Any SCA collector (Snyk, Semgrep, etc.)
.sca.vulnerabilities.critical number SCA collector
.sca.vulnerabilities.high number SCA collector
.sca.vulnerabilities.medium number SCA collector
.sca.vulnerabilities.low number SCA collector
.sca.vulnerabilities.total number SCA collector
.sca.summary.has_critical boolean SCA collector (preferred)
.sca.summary.has_high boolean SCA collector (preferred)
.sca.summary.has_medium boolean SCA collector (preferred)
.sca.summary.has_low boolean SCA collector (preferred)
.sca.findings[] array SCA collector — names the offending packages/CVEs in the max-severity failure, drives ignore_unfixable, and populates the optional webhook alert (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.

Installation

Add to your lunar-config.yml:

policies:
  - uses: github://earthly/lunar-lib/policies/sca@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
      # alert_url: "https://example.com/hook"  # Optional: webhook on max-severity failure
      # alert_timeout_sec: "2"                  # Optional: POST timeout (seconds)

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 it means a single unfixable upstream advisory can hold a gate closed indefinitely — no change to your own code can clear it. Set ignore_unfixable: "true" to narrow the failure to findings that carry an upgrade target:

    with:
      min_severity: "high"
      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. Where two scanners disagree, the one that found a fix wins — a fix that exists is actionable.
  • If the collector reported only summary counts and no .sca.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. .sca.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.

Webhook Alerts

When the max-severity check fails (a scan turned up findings at or above min_severity) and alert_url is set, the check additionally POSTs a webhook describing those findings — so you can forward CVE findings to a chat channel, an incident tool, or a custom service the moment a policy fails, without polling the Hub. Alerting is opt-in: leave alert_url unset (the default) and no webhook is ever sent.

The webhook is additive and best-effort — it does not replace the failure. max-severity fails exactly as it would without alerting; the POST is a side effect with a short timeout, and a slow, unreachable, or erroring endpoint never changes the check result (a failing check stays FAILED — it does not become an ERROR) and never adds unbounded latency.

Enable it by setting alert_url in the with: block above. If the endpoint needs authentication, set the ALERT_AUTH_TOKEN secret and it is sent as an Authorization: Bearer <token> header. Secrets are scoped per plugin type, so set it for policies explicitly:

printf '%s' "$TOKEN" | lunar secret set --scope policy ALERT_AUTH_TOKEN

Payload — a single POST with a JSON body (Content-Type: application/json). The schema is defined in webhook.py, which is dependency-free (Python stdlib only) so other policies can reuse it:

{
  "schema_version": 1,
  "policy": "sca",
  "check": "max-severity",
  "component": "github.com/acme/api",
  "git_sha": "1a2b3c4",
  "pr": 42,
  "min_severity": "high",
  "message": "High vulnerability findings detected: high: golang.org/x/net — CVE-2023-44487 (fix: 0.17.0)",
  "findings": [
    { "id": "CVE-2023-44487", "severity": "high",
      "package": "golang.org/x/net", "fix_version": "0.17.0" }
  ],
  "findings_text": [
    "high: golang.org/x/net — CVE-2023-44487 (fix: 0.17.0)"
  ],
  "run_id": "1a2b3c4",
  "dedupe_key": "9f86d081884c7d65...",
  "timestamp": "2026-06-10T12:34:56Z"
}
Field Meaning
policy / check the lunar policy and the check that fired (sca / max-severity)
component / git_sha / pr the component, commit, and PR number (pr is null when not a PR run)
min_severity the configured threshold that was crossed
message the same human-readable summary raised on the PR / in the UI
findings machine-readable findings at or above min_severity (id=CVE)
findings_text one human-readable line per finding
dedupe_key stable hash of component + git sha + finding ids (excludes timestamp) — re-running the same commit yields the same key, so consumers can drop duplicate alerts

If the collector reports only summary counts (no per-finding .sca.findings[]), findings / findings_text are empty but message is still populated.

Performance: the POST happens only on a max-severity failure with alert_url set — passing components, sub-threshold components, and alerting-disabled policies add zero network time. It is one synchronous POST per evaluation with a configurable timeout (alert_timeout_sec, default 2s).

Requirements: the POST is an outbound request from the policy runtime, so the runner must permit network egress to the endpoint (an egress allowlist would need the endpoint allowlisted). If egress is blocked, the check still fails on the findings as normal and the delivery failure is logged to stderr.

Examples

Passing Example

{
  "sca": {
    "source": { "tool": "snyk", "integration": "github_app" },
    "vulnerabilities": { "critical": 0, "high": 0, "medium": 3, "total": 8 },
    "summary": { "has_critical": false, "has_high": false }
  }
}

Failing Example

{
  "sca": {
    "source": { "tool": "snyk", "integration": "github_app" },
    "vulnerabilities": { "critical": 1, "high": 1, "medium": 10, "total": 25 },
    "findings": [
      { "severity": "critical", "package": "lodash", "version": "4.17.19",
        "cve": "CVE-2021-23337", "fix_version": "4.17.21" },
      { "severity": "high", "package": "axios", "version": "1.3.0",
        "cve": "CVE-2023-45857", "fix_version": null }
    ],
    "summary": { "has_critical": true, "has_high": true }
  }
}

Failure messages:

  • executed: "No SCA scanning data found. Ensure a scanner (Snyk, Semgrep, etc.) is configured."
  • max-total: "Total vulnerability findings (25) exceeds threshold (10)"

max-severity fails with a headline assertion plus one assertion per offending finding (most severe first), which the hub renders as a sub-list under the check in the GitHub PR comment:

max-severity

  • Critical vulnerability findings detected
  • critical: lodash — CVE-2021-23337 (fix: 4.17.21)
  • high: axios — CVE-2023-45857 (no fix available)

There's no policy-side cap — every in-scope finding is its own assertion. The hub truncates the display (it shows the first few, then N more assertions weren't shown), while the Grafana dashboard and the component JSON carry the complete list. If the collector reports only summary counts with no per-finding .sca.findings[], the check fails with the headline alone (e.g. Critical vulnerability findings detected). The webhook payload's message is that same headline; the full findings ride in its structured findings array.

Remediation

When this policy fails, you can resolve it by:

  1. executed failure: Configure an SCA scanner (Snyk, Semgrep Supply Chain, Dependabot) in your CI pipeline or as a GitHub App integration.
  2. max-severity failure: Review and remediate the flagged vulnerabilities by updating to patched versions or using your scanner's ignore feature for accepted risks.
  3. max-total failure: Reduce total vulnerability count by updating 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

Common Use Cases

Explore how individual guardrails work with specific integrations.

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