<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: CodePawl</title>
    <description>The latest articles on Forem by CodePawl (@codepawl-org).</description>
    <link>https://forem.com/codepawl-org</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12821%2F208128ff-04d5-4fd8-bab6-66c66ef51a3d.png</url>
      <title>Forem: CodePawl</title>
      <link>https://forem.com/codepawl-org</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/codepawl-org"/>
    <language>en</language>
    <item>
      <title>axios Got Hijacked Today: A Technical Breakdown of the Most Sophisticated npm Supply Chain Attack Yet</title>
      <dc:creator>CodePawl</dc:creator>
      <pubDate>Tue, 31 Mar 2026 08:15:59 +0000</pubDate>
      <link>https://forem.com/codepawl-org/axios-got-hijacked-today-a-technical-breakdown-of-the-most-sophisticated-npm-supply-chain-attack-2i82</link>
      <guid>https://forem.com/codepawl-org/axios-got-hijacked-today-a-technical-breakdown-of-the-most-sophisticated-npm-supply-chain-attack-2i82</guid>
      <description>&lt;p&gt;If you use axios — and statistically, you do — you need to read this.&lt;/p&gt;

&lt;p&gt;On March 31, 2026, two malicious versions of axios were published to npm: &lt;code&gt;1.14.1&lt;/code&gt; and &lt;code&gt;0.30.4&lt;/code&gt;. The attacker hijacked a lead maintainer's npm account, injected a hidden dependency that deploys a cross-platform RAT, and designed the entire payload to self-destruct after execution. The malicious versions were live for roughly 3 hours before npm pulled them.&lt;/p&gt;

&lt;p&gt;This isn't a typosquat. This isn't a random package nobody uses. This is &lt;strong&gt;axios&lt;/strong&gt; — 100M+ weekly downloads, present in virtually every Node.js project that touches HTTP.&lt;/p&gt;




&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;The attacker compromised the npm account of &lt;code&gt;jasonsaayman&lt;/code&gt;, the primary axios maintainer. They changed the account email to an anonymous ProtonMail address (&lt;code&gt;ifstap@proton.me&lt;/code&gt;) and published the poisoned packages &lt;strong&gt;manually via npm CLI&lt;/strong&gt;, completely bypassing the project's GitHub Actions CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;The key forensic signal: every legitimate axios 1.x release is published via GitHub Actions with npm's OIDC Trusted Publisher mechanism — cryptographically tied to a verified workflow. &lt;code&gt;axios@1.14.1&lt;/code&gt; has no OIDC binding, no &lt;code&gt;gitHead&lt;/code&gt;, no corresponding GitHub commit or tag. It exists only on npm.&lt;/p&gt;

&lt;p&gt;The attacker likely obtained a &lt;strong&gt;long-lived classic npm access token&lt;/strong&gt;. The OIDC tokens used by legitimate releases are ephemeral and scoped — they can't be stolen in the traditional sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attack chain
&lt;/h2&gt;

&lt;p&gt;The attack was pre-staged 18 hours in advance. Here's the timeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mar 30, 05:57 UTC&lt;/strong&gt; — &lt;code&gt;plain-crypto-js@4.2.0&lt;/code&gt; published (clean decoy, establishes npm history)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mar 30, 23:59 UTC&lt;/strong&gt; — &lt;code&gt;plain-crypto-js@4.2.1&lt;/code&gt; published (malicious payload added)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mar 31, 00:21 UTC&lt;/strong&gt; — &lt;code&gt;axios@1.14.1&lt;/code&gt; published via hijacked account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mar 31, 01:00 UTC&lt;/strong&gt; — &lt;code&gt;axios@0.30.4&lt;/code&gt; published (legacy branch, 39 min later)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mar 31, ~03:15 UTC&lt;/strong&gt; — npm pulls both malicious axios versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both malicious axios versions add exactly one new dependency: &lt;code&gt;plain-crypto-js@^4.2.1&lt;/code&gt;. This package is &lt;strong&gt;never imported or required anywhere&lt;/strong&gt; in the axios source. Its sole purpose is to execute a &lt;code&gt;postinstall&lt;/code&gt; hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// axios@1.14.0 deps: follow-redirects, form-data, proxy-from-env&lt;/span&gt;
&lt;span class="c1"&gt;// axios@1.14.1 deps: follow-redirects, form-data, proxy-from-env, plain-crypto-js ← new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A dependency that exists in &lt;code&gt;package.json&lt;/code&gt; but has zero usage in the codebase is a high-confidence indicator of a compromised release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inside the dropper
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;setup.js&lt;/code&gt; file (4209 bytes, minified) uses a two-layer obfuscation scheme:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;XOR cipher&lt;/strong&gt; with key derived from &lt;code&gt;"OrDeR_7077"&lt;/code&gt; — only the digits &lt;code&gt;7,0,7,7&lt;/code&gt; survive JavaScript's &lt;code&gt;Number()&lt;/code&gt; parsing, rest becomes &lt;code&gt;NaN → 0&lt;/code&gt; in bitwise ops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reverse + base64 decode&lt;/strong&gt; as an outer layer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once decoded, it dynamically loads &lt;code&gt;child_process&lt;/code&gt;, &lt;code&gt;os&lt;/code&gt;, and &lt;code&gt;fs&lt;/code&gt; at runtime to evade static analysis, then branches by platform:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Writes an AppleScript that downloads a RAT binary to &lt;code&gt;/Library/Caches/com.apple.act.mond&lt;/code&gt; — a path mimicking an Apple system daemon. Executed via &lt;code&gt;osascript&lt;/code&gt;, then the script self-deletes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Copies PowerShell to &lt;code&gt;%PROGRAMDATA%\wt.exe&lt;/code&gt; (disguised as Windows Terminal), writes a VBScript that fetches and runs a hidden PowerShell RAT with &lt;code&gt;-ExecutionPolicy Bypass -WindowStyle Hidden&lt;/code&gt;. Both temp files self-delete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux:&lt;/strong&gt; Direct &lt;code&gt;curl&lt;/code&gt; to download a Python RAT to &lt;code&gt;/tmp/ld.py&lt;/code&gt;, executed via &lt;code&gt;nohup&lt;/code&gt; to detach from the process tree.&lt;/p&gt;

&lt;p&gt;All three payloads phone home to &lt;code&gt;sfrclak.com:8000&lt;/code&gt; with platform-specific POST bodies (&lt;code&gt;packages.npm.org/product0|1|2&lt;/code&gt;) — deliberately crafted to look like npm registry traffic in network logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The self-destruct sequence
&lt;/h2&gt;

&lt;p&gt;After launching the payload, &lt;code&gt;setup.js&lt;/code&gt; performs three cleanup steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deletes itself (&lt;code&gt;fs.unlink(__filename)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Deletes &lt;code&gt;package.json&lt;/code&gt; (which contains the &lt;code&gt;postinstall&lt;/code&gt; hook)&lt;/li&gt;
&lt;li&gt;Renames a pre-staged &lt;code&gt;package.md&lt;/code&gt; to &lt;code&gt;package.json&lt;/code&gt; — a clean manifest with no scripts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Post-infection, &lt;code&gt;node_modules/plain-crypto-js/&lt;/code&gt; looks completely clean. &lt;code&gt;npm audit&lt;/code&gt; won't flag it. Manual inspection won't catch it. But the &lt;strong&gt;existence of the directory itself&lt;/strong&gt; is proof the dropper ran — &lt;code&gt;plain-crypto-js&lt;/code&gt; is not a dependency of any legitimate axios version.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check if you're affected
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check lockfile for compromised versions&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"1&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;14&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;1|0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;30&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;4"&lt;/span&gt; package-lock.json

&lt;span class="c"&gt;# Check for the malicious dependency&lt;/span&gt;
&lt;span class="nb"&gt;ls &lt;/span&gt;node_modules/plain-crypto-js 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AFFECTED"&lt;/span&gt;

&lt;span class="c"&gt;# Check for RAT artifacts&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /Library/Caches/com.apple.act.mond 2&amp;gt;/dev/null  &lt;span class="c"&gt;# macOS&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /tmp/ld.py 2&amp;gt;/dev/null                           &lt;span class="c"&gt;# Linux&lt;/span&gt;
&lt;span class="nb"&gt;dir&lt;/span&gt; &lt;span class="s2"&gt;"%PROGRAMDATA%&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="s2"&gt;t.exe"&lt;/span&gt; 2&amp;gt;nul                        &lt;span class="c"&gt;# Windows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Remediation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pin to safe versions:&lt;/strong&gt; &lt;code&gt;axios@1.14.0&lt;/code&gt; (1.x) or &lt;code&gt;axios@0.30.3&lt;/code&gt; (0.x)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove the malicious package:&lt;/strong&gt; &lt;code&gt;rm -rf node_modules/plain-crypto-js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If RAT artifacts found:&lt;/strong&gt; assume full system compromise, rotate ALL credentials (npm tokens, SSH keys, cloud keys, CI/CD secrets, &lt;code&gt;.env&lt;/code&gt; values)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit CI/CD pipelines&lt;/strong&gt; for any runs that installed during the window&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block the C2:&lt;/strong&gt; &lt;code&gt;sfrclak.com&lt;/code&gt; / &lt;code&gt;142.11.206.73&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;This is the same pattern we've seen accelerating throughout 2025-2026: maintainer account hijack → manual npm publish → phantom dependency → postinstall dropper. The Shai-Hulud worm, the Qix compromise, Chalk/Debug — all variations on the same playbook.&lt;/p&gt;

&lt;p&gt;The uncomfortable truth: &lt;strong&gt;npm's security model has a single-point-of-failure problem.&lt;/strong&gt; Long-lived tokens still exist. Email changes don't require additional verification. Manual CLI publishing can bypass every CI/CD safeguard a project has built. Trusted Publishing (OIDC) is available but not enforced.&lt;/p&gt;

&lt;p&gt;Some practical defenses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;npm ci --ignore-scripts&lt;/code&gt;&lt;/strong&gt; in all CI/CD pipelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set &lt;code&gt;ignore-scripts=true&lt;/code&gt;&lt;/strong&gt; in &lt;code&gt;~/.npmrc&lt;/code&gt; for local dev (opt-in to postinstall only when needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use lockfiles religiously&lt;/strong&gt; and review diffs on dependency changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bun and pnpm&lt;/strong&gt; don't execute lifecycle scripts by default — worth considering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package cooldown policies&lt;/strong&gt; — most malicious packages are caught within 24 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The window of exposure was ~3 hours. The detection came from Socket and StepSecurity within minutes. But for a package with 100M+ weekly downloads, even 3 hours is a massive blast radius.&lt;/p&gt;

&lt;p&gt;Pin your versions. Audit your lockfiles. Don't trust &lt;code&gt;latest&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: &lt;a href="https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan" rel="noopener noreferrer"&gt;StepSecurity&lt;/a&gt;, &lt;a href="https://socket.dev/blog/axios-npm-package-compromised" rel="noopener noreferrer"&gt;Socket&lt;/a&gt;, &lt;a href="https://www.aikido.dev/blog/axios-npm-compromised-maintainer-hijacked-rat" rel="noopener noreferrer"&gt;Aikido&lt;/a&gt;, &lt;a href="https://github.com/axios/axios/issues/10604" rel="noopener noreferrer"&gt;axios GitHub issue #10604&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://x.com/lunovian" rel="noopener noreferrer"&gt;An&lt;/a&gt; — founder of &lt;a href="https://codepawl.com" rel="noopener noreferrer"&gt;Codepawl&lt;/a&gt;, building open-source developer tools from HCMC, Vietnam.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://codepawl.com" rel="noopener noreferrer"&gt;codepawl.com&lt;/a&gt; · &lt;a href="https://x.com/lunovian" rel="noopener noreferrer"&gt;X @lunovian&lt;/a&gt; · &lt;a href="https://x.com/codepawl" rel="noopener noreferrer"&gt;X @codepawl&lt;/a&gt; · &lt;a href="https://discord.gg/7fydHgK6kA" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>security</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
