Back to Blog

Security Automation Developers Do Not Route Around

Security Automation Developers Do Not Route Around cover image

A security team I worked alongside was proud of their pipeline. Every merge request ran six scanners. The report was thorough — usually somewhere north of two hundred findings.

Nobody read it. Developers had learned that the security stage was noise, so they clicked through it the way you click through a cookie banner. When a genuinely serious finding appeared — a hardcoded credential in a new service — it sat in that report for three weeks alongside a hundred and ninety low-severity notices about test fixtures.

The tooling was not the failure. The signal-to-noise ratio was. And that, more than any scanner choice, is what DevSecOps actually is: making security checks that engineers trust enough to act on, rather than checks they learn to route around.

The Only Metric That Matters

Not coverage. Not number of tools. What percentage of the findings you surface get fixed?

If it is above 80%, your pipeline is working — engineers believe the output and act on it. If it is below 20%, you have built an expensive way to generate a report nobody reads, and you would get better security outcomes from three well-chosen checks than from six comprehensive ones.

Everything below follows from optimising that number.

Block Few Things, Loudly

The instinct when adopting security tooling is to turn everything on. It is the wrong instinct, because a gate that fires constantly gets an exception process, and an exception process that is used daily is not a gate.

What I block a merge on:

  • A secret in the code. Never negotiable, no exceptions, and scan the full history rather than just the diff — the credential that leaks is usually one committed eighteen months ago by someone who has left.

  • A critical vulnerability with a fix available in a dependency you actually use at runtime. All three conditions matter. Critical, fixable, and reachable.

  • Infrastructure code that opens something to the world. A public storage bucket or a database with a public endpoint, caught in a plan, costs nothing to fix.

  • An unsigned or unscanned image reaching production.

Everything else goes to a report with an owner and a review cadence — not to the merge gate. Four gates that engineers respect beat twelve they have learned to override.

Reachability Is What Makes Dependency Scanning Usable

The most common source of noise is dependency scanning without context. A critical CVE in a library that only your build tooling touches is not a critical risk to your service, but a naive scanner reports it identically to one in your request-handling path.

Two filters cut the volume dramatically. Separate production dependencies from development ones, because a vulnerability in a test framework is not in your attack surface. And use tooling that does reachability analysis — determining whether your code actually calls the vulnerable function — which routinely eliminates the majority of findings as genuinely inapplicable.

What remains after those filters is a list short enough that someone reads it. That is the point.

Where the Checks Belong

The earlier a check runs, the more likely it is to be acted on, because the code is still in the author's head.

Pre-commit, on the developer's machine: secret detection. Fast, and it prevents the credential from ever entering history, which is much cheaper than rotating it afterwards.

On the merge request: static analysis limited to changed files, dependency scanning on production dependencies, infrastructure-as-code scanning, and a container image scan. This whole stage should finish in a few minutes. Past ten, people stop waiting for it and start context-switching, and your feedback loop is broken.

After merge, on a schedule: the full-history secret scan, deeper static analysis, dynamic scanning against a running environment, and a re-scan of images already in production — because a base image that was clean when you built it three months ago is not clean now.

That last one is the check most teams are missing. Vulnerabilities are discovered after you ship. A pipeline that only scans at build time tells you nothing about what is running today.

name: security
on:
  pull_request:
  schedule:
    - cron: "0 3 * * 1"          # weekly re-scan of what is deployed

jobs:
  fast-gates:                     # runs on every PR, blocks the merge
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - name: Secrets (full history)
        uses: gitleaks/gitleaks-action@v2
      - name: Infrastructure config
        run: trivy config --exit-code 1 --severity HIGH,CRITICAL .
      - name: Dependencies (runtime only)
        run: trivy fs --scanners vuln --severity CRITICAL \
               --ignore-unfixed --exit-code 1 .

--ignore-unfixed deserves a mention. A critical vulnerability with no patch available is real information, and blocking a merge on it accomplishes nothing except teaching people to bypass the gate. Report it, track it, do not gate on it.

Container Security Is Supply Chain Security

Most of what a scanner finds in your image is not your code. It is the base image and everything installed into it, which means the highest-leverage security work happens in the Dockerfile rather than in the scanner configuration.

Ship less. A multi-stage build that leaves the compiler, the package manager and the source behind removes most findings by removing most software. Distroless or minimal base images take it further. You cannot have a vulnerability in a shell you did not include.

Pin base images by digest, not by tag. A tag moves; a digest does not. Reproducibility matters for incident response — you need to know exactly what was running.

Do not run as root. One line in the Dockerfile, and it turns a container escape from a serious event into a much smaller one.

Generate an SBOM at build time. When the next widely-exploited library vulnerability is announced, the question "are we affected?" should take minutes to answer, not days of grepping through repositories. That is what a stored inventory of what is in each image gives you.

Sign your images and verify signatures at deploy. This closes the gap between "we built a safe image" and "the thing running in production is the image we built."

The Cultural Half, Which Is the Larger Half

The technical setup is a week of work. Getting it used is the ongoing part.

Findings need an owner and a deadline, or they are decoration. An unassigned vulnerability list grows forever. Route findings to the team that owns the service, with an agreed timeframe by severity, and track the ones that age past it.

Fix the pipeline when it is wrong. A false positive that stays in the report for a month teaches everyone the report is unreliable. Tuning out noise is security work, not a shortcut.

Make the secure path the easy path. If using short-lived cloud credentials is harder than pasting a long-lived key into a secrets store, people will paste the key. Provide the working example, the module, the template. Most insecure choices are convenience choices.

Do not punish the person who finds the problem. The engineer who reports that they accidentally committed a credential should get help rotating it, not a lecture. The alternative is people quietly hoping nobody notices, which is strictly worse.

What I Would Set Up First

On a project with no security automation at all, in this order: secret scanning on commits and full history. Infrastructure-as-code scanning before resources exist. Dependency scanning limited to production dependencies with fixes available. Image scanning at build and on a weekly schedule against what is deployed. Then an owner and a review rhythm for whatever those produce.

That is perhaps two days of setup, and it catches the overwhelming majority of what actually causes incidents. Everything beyond it is refinement — and refinement matters much less than whether anyone reads the output.

Related Posts