The Axios Attack Is Different
TeamPCP's third major supply chain hit in two weeks targeted foundational npm infrastructure, not an AI tool. Every JavaScript agent framework was in scope.
If you ran npm install in any project with an axios dependency between 00:21 and roughly 02:30 UTC today, check your lockfile. Versions 1.14.1 and 0.30.4 both shipped a RAT. npm has pulled them, but the window was real and the malware self-deletes after execution, so inspecting node_modules after the fact won’t tell you much.
This is the third major supply chain hit from TeamPCP in two weeks. They compromised Trivy on March 19, KICS on March 23, and LiteLLM on March 25. Each of those targets made a kind of sense: security scanners and an AI proxy library, tools that run with elevated access in CI/CD. Axios is different. It’s not an AI tool. It’s not a security tool. It’s an HTTP client with roughly 100 million weekly downloads and 174,000 dependent packages. It’s plumbing.
The mechanics
The attack followed the same playbook as the LiteLLM compromise. The attacker took over the npm account of jasonsaayman, the primary axios maintainer, changed the registered email to an attacker-controlled ProtonMail address (ifstap@proton.me), and published two poisoned versions manually via npm CLI. Neither version had a corresponding commit, tag, or release on GitHub. Legitimate axios releases ship through GitHub Actions with OIDC Trusted Publisher binding. These bypassed that entirely.
The change in both versions was a single line in package.json: a new dependency on plain-crypto-js@^4.2.1. That package is never imported anywhere in the axios source. It exists solely to run a postinstall hook.
The dependency was pre-staged about 18 hours earlier from a separate throwaway account. A clean decoy version (4.2.0) was published first to build registry history, then the weaponized 4.2.1 at 23:57 UTC on March 30. The attacker waited, then published the poisoned axios versions starting at 00:21 UTC on March 31. Both branches, 1.x and legacy 0.x, were hit within 39 minutes of each other. That’s deliberate coverage. Someone who pins to the 0.x line specifically to avoid breaking changes was still in scope.
The RAT
The setup.js postinstall script is an obfuscated dropper that downloads platform-specific second-stage payloads from sfrclak.com on port 8000. It targets macOS, Windows, and Linux. After execution, the script deletes itself. The installed package directory looks clean post-install. A standard audit of node_modules reveals nothing.
The second-stage RAT is a compiled C++ binary on macOS and Linux, and an executable on Windows. It fingerprints the system on first run: user directories, filesystem roots, running processes. Then it enters a 60-second beacon loop back to the C2, waiting for commands.
What it can do: execute arbitrary shell commands, enumerate the filesystem, run additional binaries in-memory, and on Windows, inject into processes. Huntress and Elastic both confirmed the RAT performs immediate reconnaissance of .ssh and .aws directories and transmits the results to C2. On Windows, it establishes persistence through a registry key and batch file. On macOS and Linux, there’s no persistence mechanism, which means it doesn’t survive a reboot but doesn’t need to. The credential harvesting happens on first run.
Several analysis teams noted the absence of cryptocurrency miners or ransomware. The reconnaissance pattern, the targeting of developer credential paths, and the self-cleaning behavior point to espionage or pre-positioning, not quick monetization. This is consistent with how TeamPCP has operated across all their compromises.
Why this one matters more
When TeamPCP hit LiteLLM, we wrote about how the harvest list read like a map of AI agent credential locations. That attack targeted the AI infrastructure layer directly. If you weren’t using LiteLLM, you weren’t affected.
Axios doesn’t work that way. It’s a transitive dependency in essentially every JavaScript project that makes HTTP requests. LangChain.js depends on it. OpenAI’s Node SDK depends on it. Vercel’s AI SDK depends on it. Every custom agent framework anyone has built on Node.js almost certainly has axios somewhere in its dependency tree. The attack surface isn’t “developers who chose to use an AI tool.” It’s “developers who write JavaScript.”
That changes the threat model. TeamPCP’s earlier attacks targeted the AI ecosystem by compromising AI-specific packages. This one targeted the AI ecosystem by compromising something underneath it. The distinction matters because it means package-level threat intelligence focused on “AI and security tooling” wouldn’t have flagged axios as high-risk. It’s an HTTP client. It’s been around since 2014. It’s about as unsexy as npm packages get.
And that’s exactly why it works. The best supply chain targets are the ones nobody thinks about. Packages that are so foundational they show up in dependency trees without anyone explicitly choosing them. The 39-minute gap between the two poisoned versions suggests the attacker understood npm resolution well enough to cover both major version branches deliberately.
What catches this
Dependency pinning with lockfile verification is the most direct prevention. If your CI runs npm ci against a committed lockfile, a new axios version doesn’t get pulled unless someone explicitly updates it. That’s table stakes and still the single most effective mitigation.
The self-deleting dropper is designed to evade post-install package audits. But the RAT itself has to do things on the host: spawn a process, make network connections to sfrclak.com:8000, read files from .ssh and .aws directories. If you’re running Rampart in preload mode on the host where npm install executes, the exec policy catches the unexpected child process spawn and the outbound HTTP connection to an unknown domain. That requires having Rampart configured before the install happens, which is a real constraint. It doesn’t help retroactively.
The credential reconnaissance is where canary tokens matter. The RAT reads .ssh and .aws directories on first contact. Snare canaries planted in those locations fire on read access regardless of which process does the reading. You don’t catch the RAT itself. You catch the fact that something unexpected just touched your credentials, which is the signal you need to trigger rotation.
The honest gap: if the RAT exfiltrates credentials and the attacker uses them from a different machine, detection has to happen before that use. The window between “RAT phones home with your AWS keys” and “attacker uses those keys” is where the race is. Canaries compress that window. They don’t eliminate it.
The pattern
TeamPCP has now crossed five ecosystems in a month: GitHub Actions, Docker Hub, PyPI, OpenVSX, and npm. The tradecraft is consistent. Compromise a maintainer account, publish manually to bypass CI/CD provenance controls, inject a dependency that does the actual damage, clean up after execution. The campaign ID hardcoded in the axios dropper (6202033) suggests operational tracking on their end, which means this is organized and ongoing.
The shift from targeting security scanners and AI libraries to targeting foundational infrastructure packages is an escalation. It suggests either growing ambition or a recognition that hitting the plumbing reaches more targets than hitting the tools built on top of it.
StepSecurity and Socket both published detailed analyses within hours. npm pulled the packages. The response was fast. But the 2-hour window between publication and takedown was enough. Any npm install that resolved axios during that window got the RAT. In a world where CI/CD pipelines run continuously and developers install dependencies dozens of times a day, two hours is a lot of time.
The question from the LiteLLM post still applies: would you know within the hour if this happened in your environment? After today, that’s not a hypothetical for a lot of teams.