DEV Community

Cover image for vens-action: reranking Trivy/Grype CVEs by real risk in CI
Fahed dorgaa
Fahed dorgaa

Posted on

vens-action: reranking Trivy/Grype CVEs by real risk in CI

If you run Trivy or Grype in CI and triage the output by CVSS, this is the thing I wish I'd had two years ago.

Quick recap. Trivy and Grype hand you a list of CVEs. CVSS is a score in a vacuum — it doesn't know whether a service runs in a private subnet behind mTLS, or sits on the open internet handling payment cards. vens reads your scan output plus a YAML describing the service (exposure, data sensitivity, business criticality, controls, compliance, …), runs every CVE through an LLM with that context, and emits a CycloneDX VEX with OWASP Risk Rating scores. You gate the build on those instead.

vens-action is the GitHub Action wrapper — install, invocation, build gate, packaged as a composite. Here's the minimum to drop it in.

What you need

  • A Trivy or Grype JSON report (you're probably running one of these already).
  • A .vens/config.yaml. Three context fields are the floor; the full annotated reference is in examples/quickstart/config.yaml.
  • An LLM API key — OpenAI, Anthropic, Google, or a self-hosted Ollama.
  • The serialNumber of your CycloneDX SBOM (or an ad-hoc one — see below).

The config

The bare minimum:

project:
  name: "checkout-api"
context:
  exposure: "internet"
  data_sensitivity: "high"
  business_criticality: "critical"
Enter fullscreen mode Exit fullscreen mode

For scoring that actually reflects your service, fill in the rest — security controls (WAF, IDS, segmentation, …), compliance requirements, availability target, free-form notes. The annotated reference lives in examples/quickstart/config.yaml. Wrong values → wrong scores, so this file deserves the same review process as the rest of your code (CODEOWNERS, PR review, the works).

The SBOM serial number

vens writes a CycloneDX VEX whose vulnerabilities[].affects[].ref entries are BOM-Link references — they must point back to the serialNumber of the SBOM the scan was produced from.

If you already have a CycloneDX SBOM for the artifact, pull the serial from it:

SBOM_UUID=$(jq -r .serialNumber sbom.cdx.json)
Enter fullscreen mode Exit fullscreen mode

If you don't have one yet, generate an ad-hoc serial and reuse it across rescans of the same service so the BOM-Link stays stable:

SBOM_UUID="urn:uuid:$(uuidgen | tr '[:upper:]' '[:lower:]')"
Enter fullscreen mode Exit fullscreen mode

Store the value as a repo variable, say vars.SBOM_SERIAL. Flag reference: vens generate --sbom-serial-number.

The workflow

.github/workflows/scan.yml:

name: scan
on: [push]
permissions:
  contents: read
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Trivy scan
        run: trivy image python:3.11-slim --format json --output report.json

      - name: vens
        id: vens
        uses: venslabs/vens-action@v0.1.0
        with:
          version: v0.3.2
          config-file: .vens/config.yaml
          input-report: report.json
          sbom-serial-number: ${{ vars.SBOM_SERIAL }}
          llm-provider: openai
          llm-model: gpt-4o
          llm-api-key: ${{ secrets.OPENAI_API_KEY }}
          fail-on-severity: critical
          enrich: "true"

      - uses: actions/upload-artifact@v4
        with:
          name: vens
          path: |
            ${{ steps.vens.outputs.vex-file }}
            ${{ steps.vens.outputs.enriched-report }}
Enter fullscreen mode Exit fullscreen mode

Each run gives you a CycloneDX VEX (vex-file), your original Trivy report annotated with Custom.owasp_score (enriched-report, when enrich: true), and per-severity counts as step outputs (count-critical, count-high, …). Pipe the counts into dashboards, PR comments, whatever you already do with scan metrics.

fail-on-severity: critical makes the step fail if any CVE comes out CRITICAL by OWASP (score ≥ 60). Drop the line if you just want artifacts and a manual review.

Things you'll probably want to tweak

  • Self-hosted models. llm-provider: ollama + llm-base-url: http://ollama.corp:11434.
  • Air-gapped runners. Build vens yourself and pass bin-path instead of version. Skips the download + checksum step entirely.
  • Pin by SHA. uses: venslabs/vens-action@<commit-sha>. Renovate and Dependabot both follow SHA-pinned actions.

Why I bothered building it

CVSS sorts vulnerabilities like a smoke detector that can't tell if you're cooking or your kitchen is on fire. A 9.8 on an internal service with no PII and no internet exposure is rarely your urgent problem. A 5.4 on the auth path with cleartext token logging probably is. Your team knows the difference — but a spreadsheet per service doesn't scale, and most tools that do contextual scoring are paid SaaS with their own opinions.

vens is OSS, Apache 2.0. The action is a thin composite around the CLI. Issues, feedback, PRs — I read them.

Top comments (0)