Your CI/CD Pipeline Has Root Access to Production. You Just Don’t Control Who Uses It.
The Axios npm attack wasn’t a zero-day. It was your update policy working exactly as designed.
On March 31, 2026, at 00:21 UTC, North Korean state operatives published two versions of Axios to npm — versions 1.14.1 and 0.30.4.
Axios is the most popular JavaScript HTTP client in existence. Over 100 million weekly downloads. Present in approximately 80% of cloud environments. The package your CI pipeline pulls automatically every time it runs.
By 03:20 UTC — two hours and fifty-nine minutes later — the malicious versions were gone. Removed. Clean.
The damage was already done.
What Actually Happened
The attack is worth studying in detail because the technique was not exotic. There was no zero-day. No sophisticated kernel exploit. The attackers used your toolchain against you, in exactly the way it was designed to work.
Step one: compromise the maintainer.
Two weeks before the attack, the threat actor — attributed by Google Threat Intelligence Group to UNC1069, a North Korea-nexus group active since at least 2018 — compromised the GitHub account of one of the Axios maintainers through social engineering. A fake Slack workspace. A manufactured Teams meeting. Standard DPRK playbook. The maintainer’s npm long-lived access token went with it.
Step two: pre-age a clean package.
Eighteen hours before publishing the poisoned Axios versions, the attacker published plain-crypto-js@4.2.0 to npm. This version was completely clean. No malware. No payload. Its sole purpose was to give the package a brief, legitimate-looking history on the registry — enough to reduce automated suspicion scores.
Step three: inject via phantom dependency.
Rather than modifying Axios source code — which would appear immediately in a GitHub diff — the attackers added plain-crypto-js as a dependency in Axios’s package.json. The Axios source stayed clean. The poison was one level down, invisible to anyone doing a casual code review.
Step four: the postinstall hook does the rest.
npm’s postinstall hook executes automatically when a package is installed. No user interaction required. No prompt. No warning. The hook ran an obfuscated JavaScript dropper named setup.js, which reached out to a command-and-control server at sfrclak[.]com and pulled a platform-specific payload — WAVESHAPER.V2, a full remote access trojan — for Windows, macOS, and Linux.
Every developer who ran npm install during that three-hour window got a backdoor. Every CI pipeline that triggered a build. Every Docker image that ran npm ci. Full remote access. Persistent. Silent.
The Three Assumptions That Made This Possible
The attack worked because the industry has collectively normalised three dangerous assumptions.
Assumption one: npm install is safe by default.
It is not. It is a request to execute arbitrary code from a global registry maintained by thousands of individual humans, any one of whom can be socially engineered, account-compromised, or coerced. The registry has no cryptographic guarantee that what you downloaded today is what you will download tomorrow from the same version string.
Assumption two: postinstall hooks are necessary and trusted.
They are neither. Postinstall hooks execute as the installing user, with full filesystem access, network access, and in CI environments — cloud credentials via environment variables. The hook runs before you have inspected what it does. This is not a bug. It is a feature npm considers acceptable.
Assumption three: staying current is responsible.
This is perhaps the most pervasive and most damaging assumption. Update policies that automatically pull the latest patch version are not security hygiene. They are attack surface. The attacker published a malicious patch version — 1.14.1, not 2.0.0 — specifically because ^1.13.0 in your package.json would accept it automatically.
Your update policy handed them the delivery mechanism.
The Pipeline Is the Attack Surface
SolarWinds taught the industry that the build pipeline is a target. Three years later, the industry updated its threat models and continued pulling live dependencies from public registries in CI.
Here is the actual threat model your pipeline presents:
Every npm install in a CI job is an unauthenticated request to a public registry. The registry serves whatever the current publisher has uploaded. The package executes code during installation with no sandboxing. In most CI environments, that execution has access to cloud provider credentials, deployment keys, and production environment variables.
Your perimeter security, your WAF, your SIEM, your SOC — none of them see this. It happens inside your trusted build environment, triggered by your own pipeline, using your own credentials.
This is not a vulnerability in npm. It is the documented, intended behaviour. The question is whether you have architected around it.
The Defence: Own Your Packages
The organisations that were immune to the Axios attack on March 31 shared one architectural property: their build pipelines never talked to the public npm registry.
They pulled from an internal mirror. With pinned digests. That had been populated days, weeks, or months earlier — before the poisoned versions existed.
This is not complicated. It is a deliberate infrastructure decision that most organisations defer indefinitely because it adds friction to the developer experience. That friction is the point.
Internal artifact registry
Run your own npm registry — Verdaccio, Nexus, or Artifactory. Configure it to proxy the public registry with a caching layer. Your CI pipelines point exclusively at the internal registry. The internal registry is the only thing that talks to the public internet for packages, and only on a scheduled basis under controlled conditions.
When the poisoned Axios versions appeared, they would never have reached an internal registry operating this way. The registry had already cached a known-good version. The poisoned window closed before the next scheduled sync.
Pinned digests, not version ranges
^1.13.0 in package.json is a promise to accept any compatible version the registry serves. It is not a pinned dependency. It is an open invitation.
Pin to exact versions. Better: pin to the sha512 integrity hash of the specific tarball you have audited. package-lock.json with npm ci gets you closer. A lockfile committed to the repository and verified against your internal registry gets you there.
The hash of the legitimate axios@1.14.0 tarball is not the hash of the poisoned axios@1.14.1. A pipeline that verifies package integrity against a known-good manifest cannot be poisoned by a new publication — there is nothing to pull.
No live registry pulls in CI
CI jobs should never initiate network requests to public package registries. This should be a hard policy enforced at the network layer, not a guideline enforced by hope.
If your Kubernetes-based build infrastructure runs jobs in isolated pods — and it should — the network policy for build pods should permit egress to your internal artifact registry and nothing else. The public npm registry should be unreachable from a CI job by default.
This one control would have made the Axios attack a non-event for your organisation regardless of whether you were running the affected version range.
Audit postinstall hooks explicitly
Every package in your dependency tree that declares a postinstall script should be a deliberate, reviewed decision. Enumerate them:
npm query ":attr(scripts, [postinstall])" --workspaces
If a package needs to run arbitrary code on your machine during installation, you should know what that code does before it runs. In most cases, the hook can be disabled without consequence. In some cases — native module compilation, for instance — it cannot. Know which is which.
The Broader Pattern
The Axios attack is not an isolated incident. It is one data point in an accelerating trend.
Over 512,000 malicious packages were identified across npm, PyPI, and other registries in 2025 alone — a 156% year-over-year increase. The attack surface is not shrinking. Nation-state actors with the patience and resources to socially engineer package maintainers over weeks are not going away.
The economics are asymmetric. Compromising one maintainer account gives an attacker access to a delivery mechanism reaching millions of systems. The investment is modest. The return — persistent access to developer machines, build pipelines, and the production environments they deploy to — is substantial.
This is the same logic as the K2 model provenance problem in AI infrastructure. When you pull a dependency you did not build from a registry you do not control, you are extending trust to every human in that package’s supply chain. That trust is not warranted by default.
What This Means for Sovereign Infrastructure
The argument for sovereign, air-gapped build infrastructure is not paranoia. It is risk arithmetic.
The alternative to owning your artifact pipeline is accepting that your build system will periodically execute code chosen by whoever last compromised a package maintainer’s email account. For three hours on March 31, 2026, that was a North Korean intelligence operation.
An internal artifact registry with pinned digests, isolated CI network policies, and no live public registry access costs you some operational overhead. It costs you nothing compared to the incident response, forensic investigation, credential rotation, and potential breach disclosure that follows a successful supply chain compromise.
The controls are not exotic. They are the same controls that air-gapped government systems have used for decades. The reason commercial organisations do not implement them is not that they are too difficult. It is that the cost of not implementing them has, until recently, been invisible — absorbed into incidents that were attributed to other causes, handled quietly, or simply never detected.
The Axios attack was detected in three hours. Most supply chain compromises are not.
The Three Things to Do on Monday
If you are running npm-based builds and you are not already operating an internal registry with pinned digests, three changes will substantially reduce your exposure:
Lock your lockfile and enforce it. Use npm ci instead of npm install in all CI pipelines. Commit package-lock.json to the repository. Treat lockfile changes as security-relevant events requiring explicit review.
Audit your postinstall hooks. Enumerate every package in your dependency tree that runs code on installation. Disable the ones you cannot justify. Know what the ones you cannot disable actually do.
Start the internal registry conversation. Verdaccio can be running in your cluster this week. It is not a six-month project. The migration of CI pipelines to point at it instead of the public registry is a configuration change. The scheduled sync policy and integrity verification take longer to get right, but the registry itself is not the hard part.
The hard part is deciding that the current posture is unacceptable.
The postinstall hook ran. Setup.js executed. The C2 connection succeeded.
Not because your security team was negligent. Because your pipeline was designed to trust the registry, and the registry was briefly controlled by someone who did not have your interests in mind.
Own your packages. The registry does not.
Sugau specialises in bare-metal Kubernetes, sovereign infrastructure, and private AI/LLMOps. If your build pipeline still talks directly to public package registries, we should talk.