.png)
GitHub Connects Code to Cloud Risk via Defender
On March 31, 2026, two new versions of Axios dropped on npm. Versions 1.14.1 and 0.30.4 looked like routine patch releases. They weren't. Both contained a hidden dependency that silently downloaded a remote access trojan from a North Korean command-and-control server. Microsoft Threat Intelligence published their analysis the next day, attributing the attack to Sapphire Sleet, a state-sponsored group focused on cryptocurrency theft and intellectual property.
Axios has over 70 million weekly downloads. That reach alone makes this the most significant npm supply chain compromise since the ua-parser-js incident in 2021. But the real story isn't the package's popularity. It's how the attack exploited a specific weakness in CI/CD pipelines: dependency installation that runs without lockfile verification or post-install script restrictions.
The attackers didn't modify Axios's source code. That's what made this hard to catch. Instead, they added a single new dependency to the package manifest: plain-crypto-js@^4.2.1. This fake package was never imported or referenced by Axios's runtime code. Its sole purpose was to execute a post-install hook.
When npm resolved and installed the dependency, a lifecycle script ran node setup.js automatically. No user interaction. The script used layered obfuscation to reconstruct strings at runtime, identified the host operating system, and connected to a Sapphire Sleet C2 server at sfrclak[.]com:8000 to fetch a second-stage payload.
The payloads were OS-specific:
/Library/Caches/com.apple.act.mond, disguised as an Apple system component. Launched via AppleScript, then the script deleted itself.%PROGRAMDATA%\wt.exe (masquerading as Windows Terminal), and set up a registry run key for persistence across reboots./tmp/ld.py and launched detached via nohup.After execution, the installer cleaned up after itself: it deleted setup.js, replaced the original package.json with a clean-looking stub, and left behind a fully functional Axios install. Anyone inspecting node_modules after the fact would see nothing suspicious. The RAT was already running.
Developer laptops are one attack surface. CI/CD runners are a much larger one. Here's why.
Most CI/CD pipelines run npm install rather than npm ci. The difference matters enormously. npm install resolves version ranges at runtime. If your package.json specified "axios": "^1.14.0", the next build after March 31 pulled 1.14.1 automatically. No PR, no diff, no human in the loop.
CI runners also tend to have access to secrets: deployment keys, cloud credentials, npm publish tokens, Docker registry passwords. The Axios RAT's capabilities included arbitrary PowerShell execution, file enumeration, and in-memory payload injection. On a CI runner with AWS credentials or a GitHub PAT in environment variables, that's a direct path to lateral movement.
Then there's the scale factor. A single developer might install Axios once and work locally for weeks. A CI pipeline reinstalls dependencies on every push, every PR, every scheduled build. One compromised package turns into hundreds or thousands of infected runner executions per day across an organization.
The Axios compromise fits a pattern that's been accelerating through 2025 and into 2026. Each incident targets a different layer of the build pipeline, but they all exploit the same fundamental trust model: CI/CD systems execute third-party code with minimal verification.
In March 2025, the tj-actions/changed-files GitHub Action was compromised. Attackers modified the action's code to dump CI runner memory, extracting secrets from over 23,000 repositories that referenced the action by mutable tag rather than pinned SHA. The attack worked because GitHub Actions' default behavior trusts whatever code a tag points to, and most workflows used @v4 instead of a commit hash.
The Ultralytics compromise in late 2024 took a different angle: attackers injected a cryptominer into the PyPI package for the popular YOLO machine learning framework by compromising a CI/CD pipeline token. The malicious build was published through the project's own trusted CI infrastructure.
Axios combines elements of both. Like tj-actions, it weaponized a widely-trusted dependency that CI pipelines consume automatically. Like Ultralytics, the malicious code was injected through the package's own distribution channel, not through typosquatting or a lookalike name. The common thread: build pipelines that don't verify what they're running.
If any system in your organization installed axios@1.14.1 or axios@0.30.4 between March 31 and when the versions were pulled, you should treat that system as compromised. Check for these indicators:
plain-crypto-js anywhere in your node_modules directories or lockfilessfrclak[.]com or IP 142.11.206.73 on port 8000/tmp/ld.py (Linux), com.apple.act.mond (macOS), or %PROGRAMDATA%\wt.exe (Windows) on build runners or developer machinesHKCU:\Software\Microsoft\Windows\CurrentVersion\Run\MicrosoftUpdate pointing to the RATIf you find any of these, rotate every secret that system had access to. Not just npm tokens. AWS keys, database credentials, GitHub PATs, Docker registry passwords, signing keys. All of them.
The Axios incident crystallizes what security teams have been warning about for years. Here's a concrete list of changes, ordered from highest impact to incremental improvements.
This is the single most important change. npm ci installs exactly what's in the lockfile. It doesn't resolve version ranges against the registry. If someone publishes a malicious patch version, npm ci won't pick it up unless the lockfile itself has been updated through a deliberate commit.
# GitHub Actions workflow
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci # NOT npm installThe Axios attack relied entirely on a lifecycle script running during installation. If your CI environment disables scripts, the malicious code never executes. Microsoft's own guidance recommends this.
npm ci --ignore-scriptsThe tradeoff: some legitimate packages need post-install scripts (native module compilation, for example). If that's your situation, use --ignore-scripts and then explicitly allow specific packages that need build steps, or run npm rebuild for only the packages that require it.
Remove caret (^) and tilde (~) ranges from critical dependencies. For transitive dependencies you don't directly control, use npm's overrides field to force specific versions:
{
"dependencies": {
"axios": "1.14.0"
},
"overrides": {
"axios": "1.14.0"
}
}The overrides field is especially important here. Even if you pin Axios directly, another package in your dependency tree might pull in Axios as a transitive dependency with a caret range. Overrides force all resolution paths to your pinned version.
Add npm audit --audit-level=critical as a CI step that blocks the pipeline on critical vulnerabilities. This won't catch zero-days, but once advisories are published (which happened quickly for Axios), it prevents compromised packages from making it through subsequent builds.
Dependabot alerts are useful for catching known compromised versions after advisories are published. But here's the counterintuitive part: Dependabot's auto-update PRs can actually increase your exposure. If Dependabot opens a PR bumping Axios from 1.14.0 to 1.14.1 and your CI runs against PR branches, the CI build itself installs the compromised version.
Microsoft's guidance for the Axios incident explicitly recommends disabling or restricting automated dependency bots for critical packages. Keep the alerts; throttle the automatic version bumps.
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "axios"
update-types:
- "version-update:semver-patch"
- "version-update:semver-minor"A Software Bill of Materials won't prevent an attack, but it dramatically reduces your response time. When a new advisory drops, you need to answer "which of our builds included this package?" within minutes, not days. Tools like syft or cdxgen can generate CycloneDX or SPDX SBOMs as part of your CI pipeline, and GitHub's dependency graph provides a lightweight alternative for repositories hosted there.
Microsoft's post-incident guidance calls out OIDC-based trusted publishing as a mitigation. This applies if you're a package maintainer: instead of storing long-lived npm tokens that can be stolen and used to publish malicious versions, OIDC tokens tie publishing to a specific CI identity. npm has supported provenance attestation since 2023, and GitHub Actions can provide the OIDC token. If you're publishing packages, this is worth the migration effort.
The timing of the Axios compromise adds urgency to several features GitHub has been building into Actions. Their 2026 security roadmap includes dependency verification for workflow runs, which would let repository admins require lockfile integrity checks before any CI job executes. It also includes improved artifact attestation that ties build outputs to specific dependency trees, making it easier to trace which builds consumed a compromised package.
These features aren't available yet, but if your team is evaluating CI/CD infrastructure, it's worth factoring in which platforms are investing in supply chain verification at the runner level. Teams using Tenki Runners for GitHub Actions already get ephemeral, isolated runner environments that limit the blast radius of any single compromised dependency install. Ephemeral runners don't carry state between builds, which means a RAT installed during one job can't persist to the next.
The Axios compromise wasn't a sophisticated zero-day exploit. The attack surface it used, npm lifecycle scripts, has been a known risk for over a decade. The technique of injecting a malicious dependency without modifying source code is documented and understood. The publication metadata for the malicious versions didn't match the project's normal CI-backed publishing pattern.
And it still worked. It worked because the npm ecosystem's default behavior is to trust packages, resolve version ranges dynamically, and execute arbitrary code at install time. Every mitigation in the checklist above is an opt-in defense against a default-open system.
The Axios incident isn't the last supply chain compromise. It's probably not even the last one this quarter. The question for CI/CD teams isn't whether another popular package will be weaponized. It's whether your pipeline will silently pull it in and run it with full access to your secrets, or whether you've already locked that down.
Tags
Recommended for you
What's next in your stack.
GET TENKI