Documentation/CI/CD gate

CI/CD gate

Your scanner finds hundreds of CVEs. The DevSecure gate fails the build only for the ones that matter now: CVEs at or above your chosen Final Priority, or listed in CISA KEV. Each blocking CVE comes with a reason, and the job summary shows a ranked table.

Only CVE IDs are sent to DevSecure. Package names and versions stay in your CI.

GitHub Actions

- name: Scan image
  run: trivy image --format json --output trivy.json my-image:latest

- name: DevSecure gate
  uses: DevSecure-Labs/ci-gate@v1
  with:
    api-key: ${{ secrets.DEVSECURE_API_KEY }}
    scan-file: trivy.json
    fail-on: high          # critical | high | medium | low | none
    fail-on-kev: "true"

The gate calls POST /api/v1/prioritize, so your API key's plan must include it.

What it reads

  • Scanner output: Trivy JSON, Grype JSON (including related GHSA to CVE IDs), SARIF, CycloneDX vulnerabilities, or any text that contains CVE IDs.
  • cves: a comma- or space-separated list, if you don't have a scan file.

How it decides

SettingDefaultEffect
fail-onhighFail when a CVE's Final Priority is at or above this band. none turns the band check off.
fail-on-kevtrueFail on any CVE in CISA KEV, whatever its band.
allowlist.devsecure-allowlistAccepted CVEs, one per line, optionally # until 2026-12-31 reason. Text after # is a comment. Expired entries, and entries whose expiry date isn't a valid YYYY-MM-DD, are enforced again and flagged.
vexnoneA VEX document (see below).
on-api-errorfailIf the API can't be reached or replies incompletely, the step fails (exit 2). Set warn to continue. A rejected key (401/403) always fails.

Final Priority is the RPS band after the CISA KEV floor and the EPSS gate. See Priority bands.

VEX: "not affected" statements

If you publish VEX for your software, or receive it from a vendor, pass it to the gate. A CVE that the VEX shows you aren't affected by stops blocking. The rules are strict on purpose:

  • Exact components only. A not_affected, fixed, resolved or false_positive statement covers only the package URL (purl) with version that it names. The scan must report that purl: Trivy PkgIdentifier.PURL, Grype artifact.purl, or CycloneDX components. A purl without a version never matches.
  • Every affected package. A CVE stops blocking only when every package the scan reports it in is covered.
  • Fail safe. If any document says a component is affected, under_investigation, exploitable or in_triage, that wins. OpenVEX not_affected statements without a justification or impact_statement are ignored, as the OpenVEX specification requires.
  • Product scope (optional). vex-product: pkg:oci/my-image@sha256:... ignores statements written for other products.
  • Formats: OpenVEX, CycloneDX VEX and CSAF VEX.
- name: DevSecure gate
  id: gate
  uses: DevSecure-Labs/ci-gate@v1
  with:
    api-key: ${{ secrets.DEVSECURE_API_KEY }}
    scan-file: trivy.json
    vex: vex/my-image.openvex.json

- name: Keep the VEX audit with the build
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: devsecure-vex-audit
    path: ${{ steps.gate.outputs.vex-audit }}

Every VEX decision is written to devsecure-vex-audit.json. Each entry records:

  • the document path and SHA-256;
  • the statement, status, justification, author and timestamp;
  • the component;
  • why the statement was or wasn't applied.

When a blocking CVE has a VEX statement that doesn't cover it, the summary says why. For example: "no VEX statement for this exact component".

Scans without package URLs (text, SARIF) can't be matched to components. vex-scope: cve applies CVE-level statements to them as an explicit opt-in, and the audit marks them as unscoped.

Outputs and exit codes

OutputMeaning
blockedNumber of blocking CVEs
checkedNumber of CVEs ranked
allowlistedCVEs that would block but are allowlisted or not affected (VEX)
vex-auditPath of the VEX audit file, when vex is set

Exit code 0 means the gate passed, 1 that it blocked, and 2 a usage or API error.

Any other CI

The gate is a single Python 3 file with no dependencies (gate.py):

export DEVSECURE_API_KEY=...   # from your CI secret store
python3 gate.py --scan-file grype.json --fail-on critical --allowlist .devsecure-allowlist --vex app.openvex.json

It writes the same summary to standard output, and to $GITHUB_STEP_SUMMARY and $GITHUB_OUTPUT when those are set.

Source, licence and pinning

The Action and gate.py are open source under the MIT licence at github.com/DevSecure-Labs/ci-gate. They are a client: only CVE IDs are sent to the API, with your key. The DevSecure Intelligence service itself (scoring, data and API) is used under the Terms of Service.

@v1 follows the latest compatible v1 release. For a fixed, reviewable dependency, pin the full commit SHA of a release tag instead.

This product uses the NVD API but is not endorsed or certified by the NVD. CVE is a trademark of The MITRE Corporation. All third-party sources and licences: Data Sources and Licences.