Skip to content
Private Beta ·invite-only access. Reach out to get in.
Back to blog
Engineering7 min read·

CI/CD Vulnerability Gating: Stop Failing Builds on CVSS Alone

A build gate that fires on every high severity finding gets switched off within a month. Here is a three lane policy that blocks on exploitability and survives a real release.

Here is how it actually ends. It is Friday, there is a customer-facing bug, and the hotfix build fails on a medium-severity finding in a transitive dependency four levels down that has no fix available and no relationship to the change.

Someone adds || true after the scanner command. The build goes green. The pipeline ships.

From that moment the gate is decorative. Nobody removes the override, because removing it breaks Friday again.

Every design decision below exists to prevent that specific moment.

A Gate Is a Pager for Your Pipeline

The useful mental model is on-call. A pager that fires only when a human must act now gets answered. A pager that fires on everything gets muted, and once muted it stays muted through the incident it was supposed to catch.

A build gate is the same instrument pointed at your pipeline. Its credibility is a budget, it is spent by false pages, and it does not refill.

So the design question is not "what should we scan for". It is "what deserves to stop a release", which is a much shorter list.

Three Lanes, Not One Threshold

Replace the single severity threshold with three lanes and route every finding into exactly one:

LaneActionRoughly what fraction
BlockBuild failsThe small tail
WarnBuild passes, a ticket is openedThe working middle
RecordWritten to the artifact, no notificationThe long tail

The point of three lanes is that nothing gets dropped and almost nothing stops the line.

Lane 1: Block, and the Four Conditions That Justify It

A finding blocks only if all four of these hold:

  1. It is exploitable in a way you care about. Present in the CISA KEV catalog, or an EPSS score above a threshold you have written down. Not "high severity".
  2. A fixed version exists. If there is no patch, the build cannot be the place this gets resolved.
  3. The affected component is reachable in the built artifact. Not a build-time dependency, not a test fixture, not a discarded layer of a multi-stage build.
  4. It is new relative to the baseline. See the delta section below.

Miss any one of the four and it is not a block. That is the entire policy, and it is deliberately hard to satisfy.

Note that severity is not among the four. Severity tells you how bad exploitation would be; it does not tell you whether anyone is exploiting it or whether you can do anything about it today. We laid out that division in CVSS vs EPSS, and a build gate is the place where the distinction stops being academic.

Lane 2: Warn and Open a Ticket

Warnings create tickets, not Slack messages.

A Slack warning is read once, by whoever happened to be looking, and then scrolls away forever. A ticket has an owner, a due date, and a presence in your metrics. If a finding is not worth a ticket, it is not worth a warning either, and it belongs in lane 3.

This is also what keeps the block lane small without losing anything: everything that fails one of the four conditions still gets tracked, it just gets tracked somewhere that does not stop a release.

Lane 3: Record Only, Straight to the Artifact

Everything else goes into a machine-readable report attached to the build. No notification, no ticket, no interruption.

This lane matters more than it looks. It is what lets you answer "were we exposed to this on 4 September" six months later, and it costs nothing at build time because writing a JSON file notifies nobody.

The --ignore-unfixed Rule

The single highest-leverage flag in this whole setup.

A finding with no available fix cannot be actioned by a build. The person whose pipeline just went red has no move available to them. Blocking on it is pure friction with zero risk reduction, and it is the most common cause of the Friday scenario.

Concretely, in the blocking lane:

  • trivy supports --ignore-unfixed, alongside --exit-code and --severity, plus a .trivyignore file for explicit suppressions.
  • grype supports --fail-on with a severity level, and its --only-fixed flag does the equivalent job.
  • npm audit supports --audit-level.

Run the unfiltered scan separately, in lane 3, so unfixable findings remain visible to the backlog. They are still real. They are just not a build problem.

One structural note worth stating because the design error is so common: Dependabot and Renovate do not gate builds. They raise pull requests. They are an excellent remediation mechanism and not a gate at all, and treating one as the other leaves you with neither.

Gate on Delta, Not on Total

Compare against the previous successful build, or against the base image, and block only on what this change introduced.

Without this rule, a team that inherits a base image carrying a thousand findings cannot ship anything until somebody fixes the base image, which means either the gate is disabled or the product stops. With it, the base image gets fixed on its own track and feature work continues, while nobody can quietly add a new exploitable dependency.

Delta gating is what makes a strict block lane politically survivable in a real organisation.

Grace Windows: The 3am Advisory Problem

An advisory published overnight should not block an unrelated hotfix at 3am.

Write a grace window into the policy: newly published findings cannot block for a fixed period, typically 24 or 48 hours, unless they are KEV-listed.

The risk cost is close to zero, because in the first 24 hours a fix usually does not exist either, which means condition 2 would have excluded it anyway. The operational benefit is large, because it removes an entire category of surprise where your pipeline breaks for reasons that have nothing to do with your change.

Break Glass, and Why It Must Be Logged Rather Than Prevented

There must be an override. Not because overrides are good, but because a gate with no escape hatch gets a permanent escape hatch built around it, and that hatch will be an || true nobody remembers adding.

Requirements for the override:

  • One documented mechanism. A specific label, environment variable or approval, not folklore.
  • A name and a reason, logged. Who overrode, what for.
  • Visible afterwards. An override that nobody reviews is a permanent policy change made by one person under time pressure.

Count them monthly. A rising override count is the clearest possible signal that your block lane is miscalibrated, and it is far better to learn that from a number than from an incident.

Wiring In Exploitability Data: KEV, EPSS and a Stack-Matched API

The four conditions need real inputs:

  • CISA KEV is a free JSON download. Cache it, refresh it hourly, join on CVE ID. This alone gets you most of condition 1.
  • EPSS scores are published daily by FIRST, also free, also a simple join.
  • For a stack-matched view, our REST API exposes GET /api/v1/cves with bearer authentication using a vai_ prefixed key, supporting filters such as since and technology, with per-key rate limiting and X-RateLimit- headers on every response. The full contract is in the API documentation, and API access is a Max-tier feature listed on the pricing page.

The stack-matched variant answers a question the raw feeds cannot: not "is this CVE being exploited" but "is this CVE being exploited and does it touch something we run".

Re-Scanning What You Already Shipped

The most under-appreciated fact about CI scanning: an image that passed the gate last month does not pass it today.

The artifact did not change. The corpus did. A vulnerability disclosed after your build is invisible to that build forever.

So schedule registry re-scans and running-workload scans independently of builds, on a timer. Gating catches what you are about to ship. Scheduled re-scanning catches what you already shipped, and it is where most real exposure actually lives.

A Reference Policy You Can Copy

Block if all of: KEV-listed OR EPSS above 0.1; AND a fixed version exists; AND the component is in the runtime artifact; AND the finding is new versus the base image.

Warn and ticket if: severity is high or critical, a fix exists, but it is not exploitable per the above. Ticket due date follows the severity tier in your remediation policy.

Record only if: no fix available, or the component is build-time only, or the finding pre-dates the baseline.

Grace window: 48 hours from CVE publication, waived for KEV entries.

Break glass: one documented mechanism, name and reason required, reviewed monthly.

Independently: re-scan the registry weekly and running workloads daily.

Two things this policy deliberately does not cover. Container-specific hardening, base image selection and runtime concerns are their own subject, handled in container security. And the deadlines behind the warn lane come from your remediation SLAs, which are set out in our patch management strategy.

Design the gate so that when it goes red, everyone believes it. That belief is the only thing the gate is actually made of.

Frequently asked questions

Should a build fail on high severity vulnerabilities?

Not on severity alone. Severity says how bad exploitation would be, not whether anyone is exploiting it or whether a fix exists. Blocking on severity alone breaks builds for findings nobody can act on, which is the fastest route to the gate being switched off. Block on exploitability plus fix availability plus reachability plus newness.

What does --ignore-unfixed do and should I use it?

It tells the scanner to report only vulnerabilities that have an available fix. Use it in the blocking lane, because a finding with no patch cannot be resolved by the person whose build just failed. Keep those findings visible in a separate report so they still reach the backlog.

How do I stop a newly published CVE from blocking an urgent hotfix?

Write a grace window into the policy: newly published findings cannot block for a fixed period, typically 24 to 48 hours, unless they appear in the CISA KEV catalog. Pair it with a logged break-glass override so genuine emergencies have one documented path rather than an improvised one.

Is scanning in CI enough on its own?

No. A CI scan describes the artifact at build time only. The vulnerability corpus changes daily while the artifact does not, so an image that passed last month may be exploitable today. Re-scan registries and running workloads on a schedule in addition to gating builds.

Stay ahead of threats

Get AI-filtered CVE alerts for your specific tech stack. Free to start.

Start for free