Which security gates belong in the delivery pipeline?

Adding every available scanner to continuous integration produces a slow pipeline and a culture of overrides. Choose gates by what they can decide reliably, and move the rest out of the blocking path.

Categories: Security engineering Series: Secure by design

What a blocking gate is for

A blocking gate has one job: to stop a change that should not reach production, at the moment when stopping it is cheapest. That is a narrow job, and most of what teams put in the blocking path does not do it.

The failure mode is familiar. Security tooling is adopted enthusiastically, every check is set to fail the build, and within a quarter the pipeline takes forty minutes and there is a well-known process for adding the override label. The gates still exist on the diagram. They have stopped deciding anything.

What went wrong is not discipline. It is that checks with different reliability characteristics were given the same authority. A test that deterministically proves this change breaks tenant isolation and a scanner that reports a medium-severity advisory in a transitive dependency are not the same kind of statement, and treating them identically devalues the first.

Three tests a blocking check must pass

Before a check blocks a merge or a deploy, it should pass all three:

  1. Deterministic. The same input produces the same verdict. A check that fails intermittently trains engineers to re-run rather than read.
  2. Fast enough to sit in the loop. If the feedback arrives after the engineer has moved on, the gate is not preventing a defect; it is scheduling an interruption.
  3. About this change. The finding must be attributable to the diff under review. A gate that fails because of pre-existing debt punishes the wrong person and is negotiated away within weeks.

A check that fails any of these is not worthless—it may be the most valuable check you run—but it belongs somewhere other than the blocking path. NCSC’s secure development guidance makes the same argument from the developer’s side: security activity has to fit the way software is actually built, or it gets routed around.

Where each common check belongs

Check Deterministic Fast This change Placement
Security unit and integration tests Yes Yes Yes Block
Secret detection in the diff Mostly Yes Yes Block
Infrastructure policy on the proposed plan Yes Yes Yes Block
Build provenance and signature verification Yes Yes Yes Block
Static analysis, new findings only Mostly Usually Yes Block on new, report on existing
Dependency vulnerabilities Yes Yes Rarely Block on a narrow subset
Container base image age and known issues Yes Yes No Scheduled, with a service-level target
Dynamic scanning of a deployed environment No No No Post-deploy, tracked
Manual penetration testing No No No Milestone, not pipeline
Fuzzing No No No Continuous, out of band

The “new findings only” pattern for static analysis is worth the setup cost. Baselining existing findings and blocking only on what the change introduced converts a tool that developers resent into one they use, and it makes the trend line the thing you manage.

The dependency gate deserves special care

Dependency scanning produces more blocking noise than any other check, because it reports on the transitive graph rather than on your code. Most teams eventually respond by raising the severity threshold, which is a blunt instrument: severity describes the vulnerability, not your exposure to it.

A better gate combines three conditions and blocks only when all hold:

  • the vulnerability is reachable from your code, established by call-graph analysis rather than presence in the lockfile;
  • it is actionable, meaning a fixed version exists or a configuration mitigates it;
  • it is material, which for most products means it appears in CISA’s Known Exploited Vulnerabilities catalogue or scores highly once environmental metrics are applied.

That last point is where CVSS version 4.0 helps, because it separates base severity from threat and environmental modifiers. A base score is a property of the flaw; the number that should influence your pipeline is the one adjusted for your deployment.

Everything else goes to a tracked queue with an owner and a target age. This is not laxity—it is the difference between a gate that stops the three things that matter and a gate that stops everything and is therefore disabled.

Build integrity is a better gate than most scanners

The highest-value blocking checks in a modern pipeline are often not about vulnerabilities at all. They are about whether the artefact being deployed is the artefact the review approved.

Verifying that a deployable was produced by the expected build system from the expected source revision, that its provenance attestation validates, and that its signature chains to a key your platform trusts, are all deterministic, fast and specific. The SLSA framework describes escalating levels of this guarantee, and the early levels are achievable without changing how teams write code.

This class of gate also resists the override problem. An engineer under deadline pressure can plausibly argue that a medium-severity advisory should not block a release. Nobody argues for deploying an artefact whose provenance does not validate.

Give overrides a shape

Some releases genuinely need to proceed with a known finding. A gate set that pretends otherwise gets bypassed outside the system, which is worse than bypassing it inside.

Design the override rather than tolerating it. A workable shape has four parts: a named authoriser who is accountable for the consequence rather than for the deadline, a stated reason recorded against the specific finding, an automatic expiry after which the gate re-applies, and a compensating action for the interval—a feature flag, a narrowed permission, a temporary rate limit, or heightened monitoring with a named recipient.

Expiry is the part teams omit, and it is the part that matters. A permanent exception becomes invisible within a quarter; an exception that re-blocks the pipeline in fourteen days either gets fixed or gets renewed by someone who has to justify it again. Renewals are also the cleanest signal you have about which gates are mis-specified: a finding class renewed repeatedly across teams is usually a gate that is asking the wrong question.

Keep the override record in the release evidence rather than in a chat thread. When a security decision is questioned months later, the useful artefact is not that the pipeline was green—it is that someone specific accepted a specific condition for a stated period.

Put slow checks on their own clock

Removing a check from the blocking path is not the same as deprioritising it. Dynamic scanning, fuzzing and manual testing find defect classes that no fast deterministic check will ever reach, and they need a schedule of their own rather than a place in the merge queue.

Two arrangements work. The first runs the slow checks continuously against a deployed environment that tracks the main branch, so findings arrive within a day of the change that caused them without holding anyone’s merge. The second attaches them to milestones—a significant architectural change, a new external interface, a release that alters the authorisation model—so the depth of testing matches the size of the change rather than its calendar position.

Either way, give the results the same treatment as the tracked dependency queue: an owner, a target age, and visibility when the age is exceeded. The distinction that matters is between blocking and not blocking, not between important and unimportant.

Measure the gates, not the findings

Finding counts are a poor management signal: they go up when you add tools and down when you tune them, with no necessary relationship to risk. Measure the gates instead.

Four numbers are usually enough: the override rate per gate, the median time from finding to fix for the tracked queue, the age of the oldest open item in that queue, and the proportion of production incidents whose root cause a gate should have caught. The last one is the only external validation the gate set has.

The SSDF frames this as reviewing and updating the practices themselves, not only their output. A gate with a high override rate is telling you something about its design. A queue whose oldest item is eleven months old is telling you the queue is a disposal mechanism rather than a work list. Both are more useful to know than how many findings a scanner produced this week.

Get the agent release checklist

Working through this decision?

Place security work inside existing delivery rather than beside it.

A little more clarity

Questions worth asking.

Start with the questions that shape a better decision.

Should a pipeline ever block on a dependency vulnerability?

On a narrow set: a vulnerability that is known to be exploited, reachable from your code, and has a fix available. Blocking on every advisory in the transitive graph produces a queue that engineers learn to bypass.

What about findings that are real but not urgent?

Route them to the backlog with an owner and a due date, and report the queue depth and age. A finding that is worth tracking is worth scheduling; a finding worth neither should not be generated.

How do we stop overrides becoming routine?

Make each override cheap to request, expensive to repeat and visible. Record who authorised it and why, expire it automatically, and review the same gate being overridden repeatedly as a gate design problem rather than a discipline problem.