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
| Setting | Default | Effect |
|---|---|---|
fail-on | high | Fail when a CVE's Final Priority is at or above this band. none turns the band check off. |
fail-on-kev | true | Fail on any CVE in CISA KEV, whatever its band. |
allowlist | .devsecure-allowlist | Accepted 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. |
vex | none | A VEX document (see below). |
on-api-error | fail | If 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,resolvedorfalse_positivestatement covers only the package URL (purl) with version that it names. The scan must report that purl: TrivyPkgIdentifier.PURL, Grypeartifact.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,exploitableorin_triage, that wins. OpenVEXnot_affectedstatements without ajustificationorimpact_statementare 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
| Output | Meaning |
|---|---|
blocked | Number of blocking CVEs |
checked | Number of CVEs ranked |
allowlisted | CVEs that would block but are allowlisted or not affected (VEX) |
vex-audit | Path 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.