When people talk about a supply chain attack, they usually describe the tactic of how the attacker got in, not the vector. There is hardly any discussion around “what the attacker did once he was inside."
For instance, "a maintainer got phished" means someone tricked a maintainer into giving up their login. Rarely is there a discussion around “what the attacker does after taking access." The difference decides which of your defenses could have stopped any such thing in your codebase.
Attack tactics like phishing a maintainer, stealing a CI token, and buying an expired domain -all belong to the same place, something you trust is now something you should not. But what form the compromise actually lands in your system differs each time? And each kind is caught by a different control, at a different stage of your pipeline.
The three layers of every attack
Most ways of classifying supply chain attacks fail because they mix the attacker’s access technique with the form of the compromise. They’re very different – but discussions often blend these two parameters.
In such cases, the clearest public model is the SLSA threat framework. It clusters threats from A to I alphabet on the basis of where in the pipeline they occur.

So think of every security-breach incident as having one answer in each of three rows: how they got in, what form the compromise took, and where it ran. Every incident has one entry from each layer.
Layer | The question | Examples |
|---|---|---|
Access technique | How did the attacker get the ability to change what you use? | Credential theft, CI workflow injection, build platform compromise, social engineering, domain acquisition |
Vector | What form did the compromise take in the artifact you received? | The five below |
Execution surface | Where did the payload run? | Laptop at install; CI runner at build; production at runtime |
Every incident has at least one entry from each layer. The axios compromise of March 2026 began with social engineering that led to maintainer account takeover (access), delivered hostile code through a phantom dependency in a trusted package (vector), and executed on install in developer and CI environments (surface). Credential compromise is the most common access technique in the record, and it sits in the first row because it explains how the attacker gained the power to publish. The second row asks what that power produced, and the answer always takes one of five forms.
The five vectors
1. A legitimate package with a flaw
The package does exactly what its author meant it to do. The author just made a mistake, and an attacker takes advantage of it. This splits into two cases, and they result very differently.
Case-1 : Known and patched. The CVE is public, a fix already exists in this case. You simply had not installed it yet. For instance, the case of Log4Shell, around December 2021. It let an attacker run code on any server simply by getting it to log a specially crafted string
Case-2 : Zero-day in a vendor product. Accellion FTA, Kaseya VSA, GoAnywhere MFT, MOVEit Transfer (CVE-2023-34362, 2,700+ organizations), and Cleo were all breached this way. Because nobody knew the bug existed, no scan or verification could have caught it in.
Only 26% of CISA's Known Exploited Vulnerabilities were fixed in 2025, down from 38%, and median remediation slipped from 32 days to 43. Exploitation is not rising because attackers changed their preference. It is rising because remediation is losing the race.
2. Hostile code in a package you already trust
In this case, the attacker takes over a component that already has plenty of users, then publishes a hostile version of it. There is no CVE, because nothing is broken. The package does exactly what its new author intended.
Sonatype counted 454,600 new malicious open source packages during 2025, up 75%, with more than 99% on npm. Volume is the less interesting number. StepSecurity confirmed 56 compromises of already-trusted components between August 2025 and 2026: six in the first six months, fifty in the next six and a half.
What changed the curve is self-propagation. Shai-Hulud, in September 2025, was the first registry-native worm. It used each victim’s stolen npm token to poison the packages that victim maintained, reaching more than 500 packages without further attacker effort. It has recurred since: a second wave in November 2025 exposing roughly 25,000 repositories, CanisterWorm in March 2026, the Miasma worm that led Microsoft to disable 73 repositories in June, and ChainDrop in August 2026.
A worm changes the arithmetics. One phished maintainer no longer means any compromised package. It means every package that maintainer can publish, then every package their downstream victims can publish.
3. A package that was never legitimate
For instance, where an attacker registers a name that AI coding assistants hallucinate. Typosquatting, dependency confusion, and slopsquatting-
This looks like vector two and behaves nothing like it. There is no trust to hijack and no legitimate upstream to rebuild from, so rebuilding from source does nothing here. The controls that work are namespace policy, resolution policy and reputation. SLSA places this class explicitly out of scope, as threat H.
4. The artifact stopped matching what was published
The source is clean and the maintainer is honest, but what you received is not what they released. This is the vector most often misfiled as vector two, and it has the most deterministic defense.
- Codecov Bash Uploader, 2021. A leaked storage credential let an attacker modify a script CI pipelines fetched directly.
- Polyfill.io, June 2024. The domain was acquired and the CDN began serving malicious JavaScript to more than 100,000 sites. No repository changed.
- tj-actions/changed-files, March 2025. Every version tag repointed to malicious code (CVE-2025-30066).
- Trivy Action, March 2026. An attacker force-pushed 76 of its 77 version tags, so every workflow referencing an existing tag silently resolved to attacker code with no change in any consumer repository. Now CVE-2026-33634, in CISA’s KEV catalog.
The common root is mutable references. A version tag that can be repointed is not an identifier; it is a pointer, and pointers move. Immutable digests with signed provenance remove this vector by construction rather than by detection, which means no model to tune and no false positives to triage.
5. No malicious artifact at all
Sometimes nothing gets poisoned. In the Salesloft Drift compromise of August 2025, attackers stole OAuth tokens (the keys that let one service reach into another) and used them to pull Salesforce data from hundreds of organizations. Okta’s support-system breach in October 2023 had the same shape. No package was tampered with, and the weak point was the supplier itself.
This is much of why the DBIR’s "third-party involvement" figure reached 48% of breaches, up 60% year over year. But that number covers a far wider problem than verifying artifacts can solve, so read it that way.

The boundary case: the producer is the adversary
This is SLSA’s threat A. No credentials were stolen, because the real maintainer is the one doing the damage. In January 2022, the author of colors.js and faker.js sabotaged both of his own packages.
XZ Utils sits right next to this. The "Jia Tan" identity spent three years earning co-maintainer status honestly, then slipped a backdoor into the release tarball (the packaged download), which did not match the project's own source code. Rebuilding from source catches XZ, precisely because of that mismatch. It does not catch colors.js, where the source and the shipped file agreed perfectly.
The incident ledger
Compare the two columns. Access techniques vary from incident to incident, yet every vector falls into one of the same five forms.
Incident | Access technique | Vector |
SolarWinds Orion, Dec 2020 | Build platform implant (SUNSPOT) | 2 |
Dependency confusion research, Feb 2021 | Namespace precedence in resolvers | 3 |
Codecov Bash Uploader, 2021 | Leaked storage credential | 4 |
Kaseya VSA, Jul 2021 | Zero-day (CVE-2021-30116) | 1 |
Log4Shell, Dec 2021 | Zero-day, then unpatched known CVE | 1 (both cases) |
colors / faker, Jan 2022 | None. The maintainer acted. | Producer (A) |
MOVEit Transfer, May 2023 | Zero-day (CVE-2023-34362) | 1 |
XZ Utils, Mar 2024 | Three years of social engineering into co-maintainership | 2, with A |
Polyfill.io, Jun 2024 | Domain acquisition | 4 |
tj-actions/changed-files, Mar 2025 | Maintainer compromise, then tag repointing | 4 |
Nx “s1ngularity”, Aug 2025 | Injectable GitHub Actions workflow, then npm token | 2 |
Salesloft Drift, Aug 2025 | Stolen OAuth tokens | 5 |
Shai-Hulud, Sep 2025 | Stolen npm tokens, then self-propagation | 2 |
Trivy, KICS, LiteLLM, Telnyx, Mar 2026 | Unsanitized CI workflow, then harvested credentials reinvested | 4, then 2 |
axios, Mar 2026 | Account takeover using a stolen classic npm token | 2 |
ChainDrop, Aug 2026 | Worm harvesting CI credentials | 2 |
Two patterns are worth pulling out.
Security tools are a favorite target.
Trivy, Checkmarx KICS, and the Bitwarden CLI were all compromised within a six-week window in 2026. These tools are built to hold elevated access across your pipeline — which is exactly what makes them worth attacking.
The blast radius is growing faster than the number of incidents.
One five-day campaign in March 2026 took 78,330 secrets from 2,186 organizations, according to CloudSEK's victim data. The bad axios versions were only live for 174 minutes, and whether a team got hit often came down to whether they ran npm install or npm ci.
One note on counting. Be skeptical of anyone who hands you a clean split of incidents by vector. Exploitation gets counted in breaches; malicious packages get counted in packages. One malicious package nobody installs counts as one. One unpatched bug that breaches 500 organizations counts as 500. Any ratio between the two is comparing units that do not match.
The asymmetry that decides your controls
If you take one thing from this piece, take this: the two largest vectors fail in different places.
- A vulnerable package is a flaw that might be exploited later, in production, if the affected code path is reachable at all. Production is the right place to look for it, because that is where it becomes exploitable.
- A malicious package is the attack itself. It runs the moment a developer machine or a CI runner installs it, takes the credentials sitting in that runner’s process memory, and never needs to reach production. By the time it shows up in a production inventory, the secrets are already gone.
Software composition analysis watches what is deployed. That is correct for vector one and structurally too late for vector two, and the same holds for runtime scanning, admission control and deploy-time image policy: each sits downstream of the moment the damage happens.
So the control point for vector two has to sit before install, at the moment a package is resolved. Not because earlier is a virtue in itself, but because there is no later point at which the outcome is still in your hands.
Limitations to Verifications
Rebuilding your application from the clean sources catches one thing: when the package you got doesn't match what was actually published. That covers pretty much a lot. However, there are some limitations to where there is nothing to catch for it-
- Zero-days in commercial vendor products. If your file transfer appliance has an unknown flaw, no amount of verified open source changes that.
- SaaS and OAuth compromise. There is no artifact to verify. The controls are token scope, rotation and third-party access review.
- A producer whose source and artifact agree. Rebuilding reproduces the sabotage faithfully, because the sabotage was in the source.
- A signature establishes who issued an artifact and that it has not changed since. It does 4. not establish that the artifact is correct or benign. Provenance answers “is this what they published”, not “should you have trusted them”.
Closing the gaps that matter most
Clean Images and Clean Libraries are built from upstream source in an isolated pipeline, then verified before publication: known vulnerabilities checked against the binary we produced rather than a manifest, static and behavioral analysis for malicious code, maintainer signals, a diff against the last known-good release, and SLSA Build Level 3 provenance signed against an immutable digest. That targets vectors one, two and four. Vector three needs resolution and namespace policy; vector five is out of scope.
Wherever you start, start with the components your CI runners install rather than the ones your scanners report in production. Those are different lists, and the first is where the last twelve months actually happened.



