Shift left became run everything on every commit
On this page
"Shift left" started as an observation about cost: a defect found while you are writing the code is cheaper to fix than one found in staging, which is cheaper than one found by a customer. That is true, uncontroversial, and useful.
What got built from it, in a lot of places, is a pipeline that runs every scanner anyone could think of on every push, takes eleven minutes, and produces four hundred findings that nobody reads.
Then somebody adds continue-on-error so it stops blocking merges, and the whole thing becomes
decorative.
What went wrong
The idea
When feedback arrives. A defect found while you are still writing the code is cheaper than one found in staging, which is cheaper than one a customer finds.
What got built
Where the tool runs. The same scanners, moved earlier in the file, on every push. Nothing about the arrival time changed except that it now blocks a merge.
Those are not the same, and conflating them produces the specific failure above.
Moving a scanner earlier does nothing on its own. It helps only if the feedback is:
- fast enough that you are still thinking about the code, and
- relevant enough that acting on it is obviously worth the interruption.
Miss either and you have not shifted anything left. You have added latency to a merge and a report to ignore.
Latency is a threshold, not a gradient
There is a real discontinuity in how people respond to feedback speed, and it is worth designing around rather than optimizing toward.
Under about a minute, feedback lands while you still have the code in your head. You fix it now, because it is cheaper than context-switching back.
Past five minutes, you have moved on. The finding arrives as an interruption to whatever you are doing next, and gets triaged rather than fixed, which means it goes on a list, and lists are where findings go to wait.
Past ten, people route around it. Not out of malice, because the alternative is not shipping.
So the design question is not "how early" but "what fits under a minute". That is a much smaller set than "everything", and it is a set worth choosing deliberately.
What belongs early
The checks that are fast, precise and about the diff:
| Fits in the first band | Why |
|---|---|
| Secret detection | fast, near-zero false positives, and the one finding that gets categorically worse with time. A committed credential is exposed from the moment it is pushed |
| Static analysis on changed files | the diff, not the repository |
| Dependency checks when the manifest changed | and not otherwise. Nothing about your lockfile changed on a CSS commit |
| Anything that fails deterministically | a check whose answer varies run to run teaches people to re-run rather than to read |
What does not fit: full dynamic scans against a deployed environment, benchmark suites needing a live cluster, anything image-based that has to pull and unpack layers. Those are valuable and they are not sixty-second checks. They belong on a schedule, or on a release, where their cost is proportionate.
The other half: only new things
Even a fast check becomes noise if it reports the same two hundred pre-existing findings on every pull request. Nobody inherited that backlog deliberately, and blocking every change on it guarantees the gate gets disabled.
Gating on what the change introduced is the only version of this that is fair to the person it stops. A gate reading the whole backlog asks a developer to answer for code they did not write, before they are allowed to ship code they did. Nobody agrees to that twice.
The feedback belongs on the pull request
A verdict a developer has to go and look for is a verdict they read once, in a log, after the build broke. Draugr posts the delta as a comment on the pull request itself, and treats that as a first-class output rather than an integration somebody bolts on.
One comment, edited in place
A sticky note per pull request, rewritten on each push rather than stacking up, so the thread stays readable after the fifth commit and the current answer is always the one at the top.
What to do, not what is wrong
The comment can carry actions rather than findings, so a dependency bump introducing forty shows as one upgrade. Set once in a template, because which shape a team wants is a property of how they review.
--publish picks the right one of GitHub, GitLab or Azure DevOps out of the CI environment, and on
a pull request that is not the only place the answer lands: the new findings go to code scanning
as well, where they annotate the lines that caused them.
None of it needs wiring. On GitHub the action's mode: auto scans both sides and posts the
comment; on GitLab the template does the same with the differential gate on by default at P1.
Gating on what a change introduced covers the outcomes of a diff and
which one the gate reads.
The honest version
Shift left is not "run security tooling early". It is:
Give a developer feedback fast enough and specific enough that fixing it is the path of least resistance.
Everything else follows. If the feedback is slow, they will not fix it. If it is about somebody else's code, they cannot. If it is about two hundred things at once, they will not start. And if it can be bypassed, it eventually will be, so the question is whether it earns its place, not whether it is mandatory.
A gate that people route around is worse than no gate, because it produces the paperwork of security without the effect.
Draugr's approach is a single descriptor deciding what applies, a gate that reads only what a change introduced, and a ranking that puts the internet-facing critical above the internal medium, so the short list is short for a reason. The shift-left guide covers the idea, gating on what a change introduced covers the mechanism, and when everything is critical, nothing is covers the ranking half.