<?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>DEV Community: AskClaw</title>
    <description>The latest articles on DEV Community by AskClaw (@askclaw).</description>
    <link>https://dev.to/askclaw</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%2Fuser%2Fprofile_image%2F3853190%2Ff2667889-df91-438c-9780-1fc65fa06919.png</url>
      <title>DEV Community: AskClaw</title>
      <link>https://dev.to/askclaw</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/askclaw"/>
    <language>en</language>
    <item>
      <title>Supply Chain Security: 4 Commands That Would Have Stopped the axios and litellm Attacks</title>
      <dc:creator>AskClaw</dc:creator>
      <pubDate>Tue, 31 Mar 2026 09:56:20 +0000</pubDate>
      <link>https://dev.to/askclaw/the-axios-attack-changed-how-i-think-about-npm-dependencies-36h5</link>
      <guid>https://dev.to/askclaw/the-axios-attack-changed-how-i-think-about-npm-dependencies-36h5</guid>
      <description>&lt;h1&gt;
  
  
  Supply Chain Security
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;"Classical software engineering would have you believe that dependencies are good (we're building pyramids from bricks), but imo this has to be re-evaluated — I've been increasingly averse to them, preferring to use LLMs to 'yoink' functionality when it's simple enough."&lt;br&gt;
— Andrej Karpathy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He's right. And this week proved it again.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happened This Week
&lt;/h2&gt;

&lt;p&gt;Two major supply chain attacks, seven days apart:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;axios (npm) — March 31, 2026&lt;/strong&gt;&lt;br&gt;
Malicious versions &lt;code&gt;1.14.1&lt;/code&gt; and &lt;code&gt;0.30.4&lt;/code&gt; published via compromised maintainer credentials. A &lt;code&gt;postinstall&lt;/code&gt; script silently dropped a RAT, called home to a C2 server, then self-destructed — leaving no trace in &lt;code&gt;node_modules&lt;/code&gt;. Millions of developers had &lt;code&gt;"axios": "^1.13.0"&lt;/code&gt; in their &lt;code&gt;package.json&lt;/code&gt;. The &lt;code&gt;^&lt;/code&gt; did the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;litellm (PyPI) — March 24, 2026&lt;/strong&gt;&lt;br&gt;
Version &lt;code&gt;1.82.8&lt;/code&gt; compromised. Exfiltrated: SSH keys, AWS/GCP/Azure credentials, Kubernetes configs, environment variables, shell history, crypto wallets, SSL private keys, CI/CD secrets, database passwords. 97M downloads/month. Only caught because the malware had an OOM bug that crashed a developer's machine. A cleaner attack would have run undetected for weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The common thread:&lt;/strong&gt; both attacks lived entirely in install scripts. One flag stops both cold.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Fix (4 Commands + 1 Config)
&lt;/h2&gt;

&lt;p&gt;Copy these into your project. You're done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Never let install scripts run&lt;/span&gt;
npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;

&lt;span class="c"&gt;# 2. Pin exact versions — strip all ^ and ~ from package.json&lt;/span&gt;
&lt;span class="c"&gt;#    "axios": "^1.13.0"  →  "axios": "1.13.0"&lt;/span&gt;

&lt;span class="c"&gt;# 3. Age-check before installing anything new — don't touch &amp;lt; 7 days old&lt;/span&gt;
npm view &amp;lt;package&amp;gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
from datetime import datetime, timezone
times = json.load(sys.stdin)
latest = max(times.items(), key=lambda x: x[1] if x[0] not in ('created','modified') else '')
published = datetime.fromisoformat(latest[1].replace('Z','+00:00'))
age = datetime.now(timezone.utc) - published
print(f'Latest: {latest[0]}, published {age.days}d ago')
"&lt;/span&gt;

&lt;span class="c"&gt;# 4. Audit known CVEs on every CI run&lt;/span&gt;
npm audit &lt;span class="nt"&gt;--audit-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bonus: lock it all in with one &lt;code&gt;.npmrc&lt;/code&gt; — commit this to your repo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .npmrc — commit this file
&lt;/span&gt;&lt;span class="py"&gt;ignore-scripts&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true       # blocks all postinstall RATs&lt;/span&gt;
&lt;span class="py"&gt;save-exact&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true           # forces exact pins on npm install (no ^ added automatically)&lt;/span&gt;
&lt;span class="py"&gt;min-release-age&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;7         # npm v11.10.0+ only — blocks packages &amp;lt; 7 days old&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines, committed to the repo. Every developer, every CI run, every &lt;code&gt;npm install&lt;/code&gt; — protected by default. No discipline required.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;code&gt;min-release-age&lt;/code&gt; requires npm v11.10.0+ (Feb 2026). Check with &lt;code&gt;npm --version&lt;/code&gt;. On older npm, use the age-check script above manually.&lt;/p&gt;

&lt;p&gt;⚠️ Security patch override: when you need to fast-track a verified CVE fix, temporarily override with &lt;code&gt;npm install &amp;lt;pkg&amp;gt;@&amp;lt;version&amp;gt; --ignore-min-release-age&lt;/code&gt; (npm v11.10.0+) or remove the line, update, then restore it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Each One Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;--ignore-scripts&lt;/code&gt; (or &lt;code&gt;.npmrc&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The entire axios and litellm attack delivery mechanism was a &lt;code&gt;postinstall&lt;/code&gt; script. This flag disables &lt;code&gt;preinstall&lt;/code&gt;, &lt;code&gt;install&lt;/code&gt;, &lt;code&gt;postinstall&lt;/code&gt;, &lt;code&gt;preuninstall&lt;/code&gt;, and &lt;code&gt;postuninstall&lt;/code&gt; for all packages. The payload never executes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.npmrc&lt;/code&gt; config bakes this into the project permanently — you don't need to remember the flag, and new contributors get the protection automatically.&lt;/p&gt;

&lt;p&gt;⚠️ Caveat: packages that compile native addons (e.g. &lt;code&gt;node-gyp&lt;/code&gt;, &lt;code&gt;bcrypt&lt;/code&gt;) need install scripts. For most web projects: safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Exact version pins
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;❌&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;silently&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;upgraded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;thousands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;projects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;axios@&lt;/span&gt;&lt;span class="mf"&gt;1.14&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"axios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.13.0"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;✅&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;doesn't&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;move&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;without&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;explicit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;commit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"axios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.13.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;^&lt;/code&gt; means "any compatible version." In practice it means "pull whatever the maintainer published today." Remove it everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The 7-day rule
&lt;/h3&gt;

&lt;p&gt;Both attacks were caught within hours — but that was luck. Sloppy malware. A competent attacker builds in a delay before the payload activates. The 7-day window filters out rushed attacks. 48 hours catches the sloppiest. 7 days catches most of the rest.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Update type&lt;/th&gt;
&lt;th&gt;Wait&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feature / minor update&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verified security patch for known CVE&lt;/td&gt;
&lt;td&gt;ASAP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package you've never used before&lt;/td&gt;
&lt;td&gt;7 days minimum + manual review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;npm audit&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This catches &lt;em&gt;known&lt;/em&gt; CVEs — it won't catch a zero-day attack like axios. But it catches the long tail of everything else. Run it in CI. Block merges on &lt;code&gt;--audit-level=high&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Standard Tools Missed This
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Why it failed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trivy, Grype, OSV-Scanner&lt;/td&gt;
&lt;td&gt;Scan for known CVEs — zero-day has no CVE yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Renovate / Dependabot&lt;/td&gt;
&lt;td&gt;Would have &lt;strong&gt;automatically pulled in&lt;/strong&gt; the malicious version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snyk&lt;/td&gt;
&lt;td&gt;Same — no CVE, no signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Socket.dev&lt;/td&gt;
&lt;td&gt;Closest to catching it — behavioral analysis flags suspicious new publishes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What actually caught the axios attack:&lt;/strong&gt; runtime network monitoring (StepSecurity Harden-Runner) spotted the C2 callback during a CI run. The malware called home. That's the only reason we know.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deeper Fix: Fewer Dependencies
&lt;/h2&gt;

&lt;p&gt;This project (creem-worker) uses zero npm dependencies by design. Pure Node.js. If the runtime provides it, we use it. If it doesn't, we write 20 lines instead of pulling in a library.&lt;/p&gt;

&lt;p&gt;Every dependency is a permanent bet that its entire supply chain stays clean — forever. Maintainer accounts get compromised. Packages get abandoned and re-registered. CI pipelines pull from npm at 3am when nobody's watching.&lt;/p&gt;

&lt;p&gt;The safest dependency is the one you never added.&lt;/p&gt;

&lt;p&gt;When an LLM can write the function in 5 minutes, that's almost always the better call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;.npmrc&lt;/code&gt; committed to repo (&lt;code&gt;ignore-scripts=true&lt;/code&gt;, &lt;code&gt;save-exact=true&lt;/code&gt;, &lt;code&gt;min-release-age=7&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;npm ci --ignore-scripts&lt;/code&gt; in all CI pipelines (belt + suspenders)&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;^&lt;/code&gt; or &lt;code&gt;~&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; dependencies&lt;/li&gt;
&lt;li&gt;[ ] Don't install packages published &amp;lt; 7 days ago&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;npm audit --audit-level=high&lt;/code&gt; gates CI&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  If You've Already Been Hit
&lt;/h2&gt;

&lt;p&gt;A developer whose team used APIFOX (also compromised this week) laid out what's actually at risk after a supply chain attack. It's not just your code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rotate immediately:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] All SSH keys on every server&lt;/li&gt;
&lt;li&gt;[ ] All API keys in &lt;code&gt;.env&lt;/code&gt; files, CI/CD secrets, and config files&lt;/li&gt;
&lt;li&gt;[ ] Exchange API keys — set &lt;strong&gt;withdrawal disabled&lt;/strong&gt; + &lt;strong&gt;IP whitelist&lt;/strong&gt; on every exchange&lt;/li&gt;
&lt;li&gt;[ ] Browser sessions — sign out everywhere, clear cookies, revoke OAuth tokens&lt;/li&gt;
&lt;li&gt;[ ] Any token that was ever in an environment variable or clipboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Crypto-specific:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private keys should never exist in plaintext on any server — if they do, assume compromised&lt;/li&gt;
&lt;li&gt;Exchange API: attacker can drain funds via wash trading even with withdrawal disabled — monitor for unusual trading activity&lt;/li&gt;
&lt;li&gt;SSH keys give attackers a way around IP whitelists — treat SSH key theft as full server compromise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For next time:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local dev: only use test API keys, never production keys&lt;/li&gt;
&lt;li&gt;SSH: use a passphrase + consider a bastion/jump host&lt;/li&gt;
&lt;li&gt;Browser: enable 2FA/MFA on everything that touches money or infra&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The attack surface isn't your code. It's everything your code touches.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>npm</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
