The package was never the target
Ask most teams what a successful supply chain attack looks like and they describe a malicious package getting installed. That is the delivery. The success condition is a valid credential leaving your CI runner, and by that measure the attackers are winning comfortably while the industry argues about CVE counts.
Two pieces landed this month that I have been reading side by side. GitGuardian wrote up what recent supply chain campaigns were actually after once they got in. Aikido wrote up why the number of critical CVEs at the biggest software companies went from under 100 a month to over 600 since spring, and why that number is the wrong thing to stare at. Put them together and you get an uncomfortable picture of a pipeline that has learned to ship faster than it can reason about what it is shipping.
Define success from the attacker's chair
GitGuardian's list of entry points is varied. Compromised maintainer accounts. Abused CI/CD workflows. Rewritten GitHub Action tags. Poisoned packages that were trusted yesterday. Their line is that whatever the door was, "the objective was remarkably consistent: get credentials."
The numbers behind that are not small. Shai-Hulud 2.0 left behind 20,649 exfiltration repositories holding 33,185 unique secrets, of which 3,760 were confirmed still valid when they were checked. The GhostAction campaign injected malicious workflows into 817 repositories across 327 GitHub users and pulled out 3,325 secrets, including PyPI, npm and DockerHub tokens, by plain HTTP POST. Nothing clever. The runner had the tokens in its environment, so the workflow read them and sent them somewhere.
And the speed. When a poisoned Axios version hit npm, Dependabot opened its first pull request within five minutes. 895 repositories were affected inside the attack window. The automation you set up to keep dependencies fresh is the same automation that hand-delivers the compromised version to your build.
So the outcome that matters is the green box. A credential with reach. GitGuardian is explicit about what it buys: it can "open another system, expose additional secrets" or "give the attacker the publishing rights needed to push malicious software to another set of victims." One publish token turns a single compromised package into the next campaign. That is the whole business model of the mini Shai-Hulud waves we have seen since the original.
Why do fresh packages keep shipping with day-zero problems
Here is where the Aikido piece comes in, because it explains the environment that lets the chain above run so freely.
The a16z and Epoch AI numbers cover 21 major software companies including Apple, AWS, Microsoft, Google and Adobe. Reported critical vulnerabilities at those companies never cleared 100 per month in four years. Since spring they are running above 600 per month. Aikido's read, which I agree with, is that the software did not get six times worse in one season. AI made finding bugs cheap. Frontier models are surfacing vulnerabilities faster than humans can validate them, including old ones that survived years of manual review.
Detection got commoditised. Remediation did not move. Aikido calls it the remediation paradox and the Verizon 2026 DBIR figures back it up: only 26% of confirmed actively exploited vulnerabilities on CISA's KEV list were fully remediated in 2025, down from 38% the year before. Median patch time went from 32 to 43 days. Even the best resourced organisations fix 30 to 40% in the first week.
| discovery (critical CVEs / month, 21 major vendors) | ||
| 2022 to early 2026 ceiling | < 100 | |
| since spring 2026 | > 600 | |
| remediation (KEV vulns fully fixed, Verizon DBIR) | ||
| 2024 | 38% | |
| 2025 | 26% | |
| median days to patch | ||
| previous year | 32 days | |
| 2025 | 43 days | |
| scoring backlog (NIST NVD) | ||
| 18 months ago | 13,000 | |
| May 2026 audit | 27,000+ | |
The NIST line deserves its own sentence. The NVD backlog went from 13,000 to over 27,000 unscored CVEs in eighteen months, and the response after a May 2026 federal audit was to stop scoring everything and only prioritise CVEs tied to federal use, critical software, or the KEV list. The shared reference the whole industry keyed its SLAs to has formally admitted it cannot keep up.
Now connect the two articles. A maintainer publishes a new version. That version is either a poisoned one, or a legitimate one with a vulnerability that an AI-assisted researcher will find in a fortnight and a CVE that NVD may or may not score. Your dependency bot pulls it in minutes. Your build runs its install scripts with your publish tokens and cloud keys in the environment. And the thing that would tell you something was wrong, a scored CVE or a vendor advisory, arrives days or weeks later if it arrives at all. Aikido's own telemetry on StyleSmuggler is the tell: they had the Adobe Commerce RCE tracked before it had a CVE number, and 67% of the packages they found vulnerabilities in were never disclosed to any public database.
Is it too fast, or do we not understand the impact
Both, and they reinforce each other.
The speed problem is real and it is self-inflicted. We built automation that treats "new version exists" as sufficient reason to bring it into the build within minutes. That was a reasonable trade when the failure mode was a broken test. It is not a reasonable trade when the failure mode is a workflow that POSTs your npm token to a stranger. Five minutes is faster than any human triage, faster than any scanner refresh, and faster than the registry's own takedown.
The impact problem is subtler and I think it is the bigger one. Most organisations have an SBOM now. Compliance asked for it, a tool generated it, it lists every component and version. What almost nobody has is the other inventory: which credentials sit in which runner, what each of them can reach, and who owns them. GitGuardian's advice is blunt about this. Build the credential map before the incident, because "waiting until the incident to build that map means reconstructing it while the response clock is already running."
An SBOM tells you the poisoned package is in 40 repos. It does not tell you that 12 of those repos build on a runner holding a production deploy key and a publish token for your own internal packages. The first fact is a remediation ticket. The second fact is the incident. Teams are reading the bill of materials and not the bill of access, and that is why the impact keeps being underestimated until the exfiltration repos show up in someone's research post.
There is a third thing that makes it worse and it is cultural. Vulnerability programmes are still run on CVE volume. Count of criticals, days open, percentage closed. When the volume goes up six-fold the honest response is that the metric broke, and instead the response is usually to work harder on the same queue. Aikido's framing is the right one: "Risk is determined by how fast you can find out which vulnerabilities matter, and how fast they get fixed once they're validated." A number that says how many CVEs exist tells you about the researchers. It does not tell you about your exposure.
What I would actually do about it
Everything below is a gate in the execution path, not a policy document. The people and agents pulling packages will not read the document. They will notice the gate only if it is implemented wrong.
1. Put a clock on new versions
We run a 48-hour minimum package age at the install layer. Every npm, pnpm, yarn, bun, pip, uv and poetry call goes through a shell alias into a local proxy that checks the package and its dependency tree against threat intel before anything hits disk. The agent running the install does not know the proxy exists. It gets a package or it gets an error. Shai-Hulud spread across 160 plus packages in hours and was publicly identified inside two days. Two days of waiting removes most of that window and costs, in practice, almost nothing.
2. Make the runner boring to rob
If the success condition is a credential leaving the runner, the fix is to stop putting credentials worth having on the runner. Short-lived OIDC federation to the cloud instead of static keys. Publish tokens that exist for one job and expire. Agent identities on 30-day keys, separate from the human ones, so the audit trail can tell them apart. GitGuardian's recommendation to scope runner permissions to exactly the "publishing credentials, source code, deployment credentials, or other secrets" a job needs is the same point. GhostAction worked because the tokens were just there.
3. Scan in CI and stop pretending pre-commit counts
We run secret detection twice. Pre-commit is a developer courtesy and anyone can skip it with one flag on a machine we do not control. The CI run is the control. It blocks the merge and nobody overrides it, because an override that exists gets used at five on a Friday. One blocking status, not six advisory ones. Six advisory checks train people to scroll past, and that habit generalises to the check that mattered.
4. Triage on reachability, and stop waiting for the number
Aikido's first recommendation is reachability analysis, filtering out findings with no execution path to the vulnerable code, and it is the only way I know to survive 600 criticals a month. Their second is not to wait for a CVE ID or a vendor advisory before acting. The StyleSmuggler case is the proof. A working fix shipped through autofix across multiple pinned versions more than 24 hours before Adobe's own hotfix. When two thirds of the vulnerable packages you find were never disclosed anywhere, a process that begins at "CVE published" is a process that begins late by design.
5. Let the fix arrive as a diff
The adoption curve of a security fix depends almost entirely on the form it arrives in. A Jira ticket that says "upgrade X" competes with the sprint. A small pull request that already passes tests gets merged before lunch. Our SCA tooling opens the remediation PR itself. Same fix. Completely different outcome, and it is the only way the remediation side of the paradox ever catches up with the discovery side.
6. Build the credential map now
Which secrets exist, where they live, what they reach, who owns them. Per repo, per runner, per agent. This is the inventory that an SBOM was never going to give you and it is the one you will need at 2am when a research team publishes a list of exfiltration repos and one of them has your org name in it. GitGuardian's post-incident sequence is scope, ownership, prioritise by reach, revoke or rotate. Every one of those steps is faster if the map already exists.
The honest costs
None of this is free and I would rather say so than have you find out. The 48-hour hold generates a real argument every so often, usually when the fix you need shipped that morning. Moving CI to short-lived credentials means AppSec owns the integration work instead of pushing tickets to teams, and it took a lot longer than a tool rollout. Reachability analysis is only as good as the call graph, and it will occasionally file a real problem under "unreachable". The credential map rots the moment you stop maintaining it, and a stale map is worse than none because people trust it.
Verdict
A supply chain attack succeeds when a credential with reach leaves your build. Not when a package gets installed, and not when a CVE gets published. The industry is counting the wrong thing at both ends: installs and CVEs, when the attacker is counting valid tokens. Six hundred criticals a month is going to be the floor from here, because the tools that find them are not getting slower. The only variable you control is how little there is to steal when one gets through, and how many hours it takes you to close it once you know.
Read the two pieces that prompted this. GitGuardian on what supply chain attacks are really after, and Aikido on why the CVE spike is a remediation problem. They are better read together than apart.
No comments:
Post a Comment