ISSUE 2026-09-27SERIES BREC 35TYPE BRIEFgarnetgrid.com

The roadmap starts with a denominator

Insights

Most DevSecOps roadmaps are procurement plans with a timeline attached. The useful version is an ordering argument: what you fix before buying anything, what you allow to fail a build, and who owns a finding once it exists. This is the sequence I would defend, including what changes when the system runs on hardware you own and cannot reach the public internet.

Most programmes begin by turning something on. Within a fortnight there is a dashboard with a large number on it, and the number is close to meaningless, because nobody knows what it is a fraction of.

Coverage dashboards flatter you by construction. They report on the repositories that were onboarded. A repository nobody registered does not appear as unscanned; it does not appear at all. The same is true of services that reach production by a route other than the standard pipeline. Absence reads as zero, and a zero reads as fine.

What you actually need first is an inventory: what runs, where, who owns it, what is inside it, and by which route code gets from a laptop to production. That last item is the one people skip, and it is the one that decides whether any gate you later build has any effect at all.

There is a test for this that needs no tooling. Pick a library your teams use widely. Ask which running services contain it, and at what version. If nobody can answer inside an hour, you have an inventory problem, and a scanner does not fix an inventory problem. It gives you a more confident wrong answer.

Fix CI's identity before you buy a single scanner

Continuous integration is usually the most privileged system in the estate and the least reviewed. It can deploy to production, it can read secrets, and it is triggered by content written by people who do not work for you.

Two mechanisms are worth understanding properly. The first is long-lived static credentials sitting in pipeline variables: they do not rotate, they are readable by any job in scope, and they outlive the person who created them. The second is the trigger variant that runs your workflow definition with repository secrets while checking out a fork's code; GitHub Actions calls this `pull_request_target`, and equivalents exist elsewhere. That one gives an outsider a plausible path to your credentials without ever landing a commit on your default branch.

The fix is unglamorous. Replace static credentials with short-lived federated ones issued per job, scoped to an environment rather than an organisation. Treat every runner as potentially compromised: ephemeral runners, no shared caches between trusted and untrusted work, no reuse of a build host across tenants.

This costs engineering time rather than licence money, and it eliminates a class of incident rather than reporting on one. It belongs before your first code scanner. It almost never gets sequenced there.

Decide what blocks, and be honest that very little should

Every blocking gate is a bet that the thing it catches is worth stopping the line for. Get that bet wrong, or make it slow, and engineers route around it. The route-around then becomes permanent, because emergency-merge rights are never handed back.

A check earns the right to block when it meets four conditions:

Secret detection passes that test. So does verifying an artefact's signature and provenance, and checking lockfile integrity. Most static analysis does not. Most container image scanning does not either, because base-image findings are usually not fixable by the author of the commit that triggered them, which fails the third condition outright.

On an existing codebase the rule is: accept what is already there, block what is new, and scope the check to the diff. Skip this and day one delivers a backlog nobody can pay down, and the programme loses credibility in a week.

There is one exception. Secrets already committed are not backlog, they are live. Rewriting history does not help you, because you cannot prove nothing cloned it. Assume disclosure and rotate. The gate stops new ones; a rotation project deals with the past.

Then watch your override rate as a first-class metric. If emergency merges are climbing, the gate is not working, whatever the compliance report says.

Severity is not risk, and reachability is only part of the gap

A published vulnerability score describes a flaw in the abstract. It does not know whether your code calls the affected function, whether the dependency ships in the production artefact or only in the test harness, or whether the vulnerable path can be reached from untrusted input at all.

Left untriaged, that mismatch produces the worst possible outcome: engineers learn that the word critical carries no information. Once that lesson is learned it is very hard to unlearn, and it applies to the one finding that genuinely mattered.

Reachability analysis narrows the gap and is worth having. Be honest about its limits, though. Reflection, dynamic dispatch, plugin loading and configuration-driven code paths all defeat static reachability, so it errs in both directions: it will clear things that are exploitable and flag things that are dead. How much noise it removes depends heavily on the language and the ecosystem, and I would not trust any general figure for it.

The artefact you must actually produce is a written triage policy. What counts as exploitable in your context. What response time each class gets. Who decides. And what accepted means, including an expiry date on the acceptance. An acceptance with no expiry is a backlog item with better manners.

A paved road beats a gate

Gates express prohibition. They create no capability, and they place the cost of doing the right thing on the person in a hurry. If the secure path is also the path of least work, adoption stops needing enforcement.

In practice a paved road is a pipeline template that already does the right things, base images patched on a cadence by someone whose job that is, a secrets mechanism easier to use than pasting a value into an environment variable, and infrastructure modules that are correct by default. None of that is exciting and all of it compounds.

The ownership split worth insisting on: security defines the check and the policy, the platform team owns the road and keeps it fast, and product teams own their own findings. Where the security team owns remediation, remediation does not happen, because they cannot ship the fix and the team that can has no incentive.

Then measure the share of production deploys that travel the road. In my experience that single proportion tells you more about programme health than any findings count, because it tells you how much of your estate your controls actually touch.

What changes when it is self-hosted or air-gapped

Most of the DevSecOps guidance I have read quietly assumes software-as-a-service: hosted CI, hosted scanners, a vulnerability feed fetched over the internet at scan time. Put the same system on hardware the customer owns, with egress restricted, and several assumptions break together.

Package resolution goes first. A build that reaches out to public registries either fails or forces you to punch a hole in the very boundary you were selling. Run an internal mirror or proxy. This is also the strongest available defence against dependency confusion and against a package disappearing underneath you, and it makes builds far more reproducible. In a restricted environment I would rank it the highest-return change, and it is infrastructure work rather than security spend.

Vulnerability data then becomes an import with its own supply chain. Your database has an as-of date. Treat that freshness as an explicit service level, record which snapshot produced each verdict, and be clear about who carries the update across the boundary and how it is verified. A scanner running against a six-month-old snapshot does not report uncertainty. It reports clean, confidently.

Audit and alerting have no vendor to phone home to, so log retention, and a human who actually reads the output, are yours. A control nobody watches is documentation.

Finally, model artefacts, which most roadmaps omit entirely. Weights are build inputs with no lockfile and no package manager worth the name. Some checkpoint formats deserialise into arbitrary code execution on load, which means downloading a model and loading it is executing untrusted code inside your inference process. The ecosystem has moved towards tensor-only formats and safer load defaults, but plenty of published artefacts remain in the older format. Pin by content hash, record provenance, prefer the tensor-only format, and load anything else inside a sandbox you would be content to lose. If you gate binaries and not weights, the widest door in the building is the one you left open.

Measure few things, and expect them to be gamed

Four numbers carry most of the signal: time to remediate by severity class; the age of the oldest open finding in each class; coverage as a fraction of the real inventory rather than the onboarded one; and the share of deploys through the paved road.

The second exists because of the first. Any metric that collapses conditions gets optimised rather than met. Set a mean time-to-fix and the cheap findings get closed while the hard ones age quietly out of sight, which the mean will happily conceal. Count findings and they get reclassified. Put the oldest-open age next to the average and that particular move stops working.

None of these tell you whether you would survive a competent attacker. They tell you whether the machine is running. Keeping that distinction explicit in the room is most of what separates a security programme from a reporting function.

So, concretely. Build the inventory and fix CI's identity first; neither needs procurement, and both remove whole classes of incident. Then turn on secret detection as a blocking gate, with a rotation project behind it for what it finds in history. Then get a real dependency inventory, stand up an internal package mirror, and commit to a patch cadence you will actually keep. Then configuration and infrastructure-as-code checks, because in my view misconfiguration and missing authorisation account for more real incidents than novel library bugs. SAST comes after that, diff-scoped and non-blocking until its false-positive rate has earned the right to stop a build. And do not buy a platform until you can say who triages a finding at two in the morning on a Tuesday. If that question has no answer, the tool will add noise, not security.

Talk to us about this