<?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: mckeane mcbrearty</title>
    <description>The latest articles on DEV Community by mckeane mcbrearty (@mckeane_mcbrearty_77fda95).</description>
    <link>https://dev.to/mckeane_mcbrearty_77fda95</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%2F3804123%2F6322e4db-63b2-4729-9c9d-f66fb385e7ab.jpg</url>
      <title>DEV Community: mckeane mcbrearty</title>
      <link>https://dev.to/mckeane_mcbrearty_77fda95</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mckeane_mcbrearty_77fda95"/>
    <language>en</language>
    <item>
      <title>I watched Shai Hulud steal credentials from teams running npm audit. Here's the gap nobody talks about.</title>
      <dc:creator>mckeane mcbrearty</dc:creator>
      <pubDate>Sat, 11 Apr 2026 16:25:51 +0000</pubDate>
      <link>https://dev.to/mckeane_mcbrearty_77fda95/i-watched-shai-hulud-steal-credentials-from-teams-running-npm-audit-heres-the-gap-nobody-talks-1ed9</link>
      <guid>https://dev.to/mckeane_mcbrearty_77fda95/i-watched-shai-hulud-steal-credentials-from-teams-running-npm-audit-heres-the-gap-nobody-talks-1ed9</guid>
      <description>&lt;p&gt;September 2025. PostHog, Zapier, Postman, ENS Domains. Over 500 packages compromised. Credentials pulled from developer machines and CI agents. When the malware found an npm token, it automatically published backdoored versions of every package that token had access to. No human needed after the first infection.&lt;/p&gt;

&lt;p&gt;Every team I watched get hit was running npm audit. Some had Snyk. One had Dependabot. All of it passed clean.&lt;/p&gt;

&lt;p&gt;I spent the next few months figuring out why, then built something to fix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with npm audit
&lt;/h2&gt;

&lt;p&gt;npm audit checks your dependency versions against a database of known vulnerabilities. If there's no advisory filed, it returns zero findings.&lt;/p&gt;

&lt;p&gt;With supply chain attacks, there's no advisory to file. The package does exactly what the attacker intended. A preinstall hook that reads &lt;code&gt;~/.npmrc&lt;/code&gt; and posts it to a webhook is working as designed. npm audit has nothing to flag.&lt;/p&gt;

&lt;p&gt;From recent attacks, all clean passes at time of compromise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;axios&lt;/strong&gt; (~100M weekly downloads) — maintainer account compromised by a North Korean state actor on March 31, 2026. A fake dependency was injected with a postinstall hook that dropped a cross platform RAT on Windows, macOS, and Linux. Live for about two hours before removal. Dependency Guardian flagged it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;xrpl&lt;/strong&gt; (~135K weekly downloads) — backdoored through a compromised maintainer account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;eslint-config-prettier&lt;/strong&gt; (~30M downloads) — hijacked via phishing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chalk and debug&lt;/strong&gt; (~299M downloads) — compromised in a coordinated attack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shai Hulud packages&lt;/strong&gt; (~2.6B combined downloads affected) — selfpropagating worm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these had CVEs when they were actively stealing credentials. The CVE came later.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually catches these
&lt;/h2&gt;

&lt;p&gt;Every major supply chain attack in the last year used the same pattern. Credential theft, network exfiltration, install script abuse. A string padding library doesn't need &lt;code&gt;child_process&lt;/code&gt;. A color utility doesn't need to make outbound HTTP calls. A patch release shouldn't add obfuscated network calls that weren't in the previous version.&lt;/p&gt;

&lt;p&gt;You don't need a database to spot any of that. You need something that reads the published tarball and checks what the code does before it runs in your pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://westbayberry.com" rel="noopener noreferrer"&gt;Dependency Guardian&lt;/a&gt; does that.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The scanner is written in Rust. It runs behavioral analysis on the published tarball before anything installs. No CVE lookup, no LLM, fully deterministic. Same package gets the same result every time.&lt;/p&gt;

&lt;p&gt;The detection covers credential theft, shell execution, network exfiltration, obfuscation, time bombs, and CI secret access. Findings don't just get flagged individually. They get correlated. A &lt;code&gt;child_process&lt;/code&gt; import by itself is a weak signal. That same package also making outbound network calls and reading environment variables in a patch release is a confirmed exfiltration pattern and it gets auto blocked. No triage queue.&lt;/p&gt;

&lt;p&gt;There's also a behavioral sandbox that runs packages in an isolated container to catch things static analysis misses, like payloads that only activate after a time delay.&lt;/p&gt;

&lt;p&gt;Validated against 133,516 real packages (53,119 malicious, sourced from DataDog's malicious packages dataset, OpenSSF, and the GitHub Advisory Database):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;99.5% detection rate on npm, 99.2% on PyPI&lt;/li&gt;
&lt;li&gt;0.6% false positive rate&lt;/li&gt;
&lt;li&gt;F1 score 99.79%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full methodology at &lt;a href="https://westbayberry.com/benchmark" rel="noopener noreferrer"&gt;westbayberry.com/benchmark&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using it in CI
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub App&lt;/strong&gt; scans the lockfile diff on every PR and posts findings as a check. A package with a credential stealing install script gets blocked before the merge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiufe1kxrtwyieb68v46f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiufe1kxrtwyieb68v46f.png" alt=" " width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI&lt;/strong&gt; for one off checks or non GitHub CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @westbayberry/dg
dg scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1t9qni94yws1o3ugjc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1t9qni94yws1o3ugjc6.png" alt=" " width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It reads your lockfile, sends package names and versions to the detection API, and returns findings. Your source code never leaves your machine.&lt;/p&gt;

&lt;p&gt;If you want to verify that's actually all it sends, the client is open source. You can read exactly what goes over the wire before you run anything: &lt;a href="https://www.npmjs.com/package/@westbayberry/dg" rel="noopener noreferrer"&gt;npmjs.com/package/@westbayberry/dg&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Free tier is 1,000 scans per month. No account needed for the CLI.&lt;/p&gt;




&lt;h2&gt;
  
  
  On false positives
&lt;/h2&gt;

&lt;p&gt;A tool that blocks PRs on weak signals gets turned off.&lt;/p&gt;

&lt;p&gt;Socket, the main competitor here, introduced three alert severity tiers because of noise. At scale, triaging medium risk alerts becomes someone's actual job. We handle that at the detection layer. Signals correlate into high confidence patterns before anything blocks. Single weak signals don't stop your pipeline, they feed a risk score.&lt;/p&gt;

&lt;p&gt;Socket also uses LLM inference on each scan, which means results vary between runs and detection depends on a third party API staying up. Our scanner is deterministic. When a block fires you can trace it to the exact rule that triggered it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing that doesn't get said
&lt;/h2&gt;

&lt;p&gt;Most attackers aren't trying to evade behavioral detection. They're using the same preinstall script playbook they've used for years because most teams have zero visibility into what their packages actually do at install time.&lt;/p&gt;

&lt;p&gt;Going from nothing to behavioral analysis blocks an entire category of attack and forces the rest into increasingly constrained patterns. The teams that move first on this have a real advantage right now, because most of their peers are still relying on a database that only knows about yesterday's attacks.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @westbayberry/dg &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; dg scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install the &lt;a href="https://github.com/apps/dependency-guardian" rel="noopener noreferrer"&gt;GitHub App&lt;/a&gt; for automatic PR scanning with no config.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How Dependency Guardian Would Have Caught Shai-Hulud</title>
      <dc:creator>mckeane mcbrearty</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:05:00 +0000</pubDate>
      <link>https://dev.to/mckeane_mcbrearty_77fda95/how-dependency-guardian-would-have-caught-shai-hulud-5956</link>
      <guid>https://dev.to/mckeane_mcbrearty_77fda95/how-dependency-guardian-would-have-caught-shai-hulud-5956</guid>
      <description>&lt;p&gt;In September 2025, the npm ecosystem experienced its first self-replicating worm. The attacker published a single malicious package under a common typosquat name. Within 24 hours, 500+ packages were compromised. By the time the second wave subsided in November, 796 packages had been infected. The worm was called Shai-Hulud.&lt;/p&gt;

&lt;p&gt;Every major CVE-based security tool — Dependabot, Snyk, npm audit — gave it a clean bill of health.&lt;/p&gt;

&lt;p&gt;This post is a technical breakdown of exactly how Dependency Guardian's behavioral analysis engine would have blocked Shai-Hulud at the PR gate, before a single infected package entered your dependency tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Shai-Hulud Did
&lt;/h2&gt;

&lt;p&gt;The attack was elegant in its simplicity. The initial payload was a preinstall script that executed a bundled JavaScript file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"colros"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&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.5.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"preinstall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node scripts/setup.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That setup.js file did four things:&lt;/p&gt;

&lt;p&gt;Read npm tokens from ~/.npmrc and environment variables (NPM_TOKEN, GITHUB_TOKEN)&lt;br&gt;&lt;br&gt;
Exfiltrated the tokens via the GitHub API — POST &lt;a href="https://api.github.com/repos/" rel="noopener noreferrer"&gt;https://api.github.com/repos/&lt;/a&gt;/shai-hulud/issues — disguised as normal GitHub traffic&lt;br&gt;&lt;br&gt;
Used Bun-specific APIs (Bun.spawn, Bun.file) to bypass Node.js security sandboxes that only monitor child_process and fs&lt;br&gt;&lt;br&gt;
Ran npm publish on every package the stolen token had write access to, injecting the same preinstall hook into each one  &lt;/p&gt;

&lt;p&gt;The self-replication loop was the breakthrough. Previous npm attacks were one-shot: compromise a package, steal data, done. Shai-Hulud was a worm. Every victim became a new attack vector.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Existing Tools Missed It
&lt;/h2&gt;

&lt;p&gt;The answer is straightforward: there was no CVE to match against.&lt;/p&gt;

&lt;p&gt;CVE-based tools work by maintaining a database of known-vulnerable package versions. When you run npm audit, it checks your lockfile against that database. If package@version appears in the database, you get an alert. If it doesn't, silence.&lt;/p&gt;

&lt;p&gt;Shai-Hulud was a zero-day. No CVE existed. No advisory had been filed. The infected packages were otherwise-legitimate libraries with new patch versions published using stolen credentials. From the perspective of npm audit, version 2.3.1 of a popular utility was just another routine update.&lt;/p&gt;

&lt;p&gt;The attack also made deliberate choices to evade heuristic tools:&lt;/p&gt;

&lt;p&gt;GitHub API for exfiltration: most network-based heuristics allowlist github.com as a known-good domain&lt;br&gt;&lt;br&gt;
Bun runtime APIs: security sandboxes that hook child_process.exec and fs.readFileSync saw nothing because the code used Bun.spawn and Bun.file instead&lt;br&gt;&lt;br&gt;
No obfuscation: the code was clean, readable JavaScript — no base64 encoding, no eval, no string concatenation tricks  &lt;/p&gt;
&lt;h2&gt;
  
  
  Which Dependency Guardian Detectors Would Fire
&lt;/h2&gt;

&lt;p&gt;Dependency Guardian doesn't ask "is this package in a vulnerability database?" It asks "what does this code do?" Each of the 26 behavioral detectors examines a specific capability. Here's what would have triggered on Shai-Hulud's payload:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Detector&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;What It Caught&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;install_script&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;preinstall hook executing node scripts/setup.js — a non-benign script running arbitrary code at install time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;child_process&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Shell command spawning during the install lifecycle (4-pass detection: static imports, high-risk calls, inline requires, dynamic identifier-aware patterns)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;network_exfil&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Outbound HTTP calls to api.github.com — the 5-pass architecture catches builtin HTTP, third-party clients, WebSocket, DNS, and global APIs like fetch regardless of the destination domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ci_secret_access&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Reading NPM_TOKEN and GITHUB_TOKEN from process.env — the 17-pattern engine with PROC/ENV/D/BO building blocks catches dot notation, bracket notation, destructuring, aliasing, and optional chaining&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;token_theft&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Reading ~/.npmrc via file system calls — 21 credential target patterns including .npmrc, .yarnrc, and path.join split-path evasions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bun_runtime_evasion&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Bun.spawn, Bun.file API usage — detects dot notation, bracket notation (Bun['spawn']), .call/.apply/.bind, globalThis.Bun, and destructured aliases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worm_behavior&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;npm publish commands executed programmatically — catches exec('npm publish'), spawn('npm', ['publish']), Bun.spawn publish variants, and execa wrappers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seven detectors. Seven independent signals that this package is doing things a color utility should never do.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Closer Look at the Detection
&lt;/h2&gt;

&lt;p&gt;Consider what the ci_secret_access detector sees when it scans setup.js:&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;// Shai-Hulud's token harvesting (simplified)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NPM_TOKEN&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;npm_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ghToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detector's 2-pass per-file architecture first runs findAliases() to discover any process.env aliasing (destructuring, variable assignment, globalThis indirection). Then buildAliasPatterns() generates dynamic regexes specific to that file. Even if the attacker had written:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NPM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The alias tracking would catch the process["env"] assignment, and the string concatenation evasion pattern would flag the token construction at severity 4.&lt;/p&gt;

&lt;p&gt;The worm_behavior detector is similarly thorough. It doesn't just grep for npm publish. It looks for the entire taxonomy of programmatic publishing:&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;// All of these trigger worm_behavior:&lt;/span&gt;
&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm publish --access public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publish&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publish&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;spawnSync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bun&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publish&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;   &lt;span class="c1"&gt;// bracket notation evasion&lt;/span&gt;
&lt;span class="nf"&gt;execa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publish&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nf"&gt;execaCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm publish&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each pattern carries a weight of 3. A single match is enough for a severity 5 finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Correlator Amplifies Signals
&lt;/h2&gt;

&lt;p&gt;Individual detectors produce findings. The correlator examines combinations of findings across detectors and generates amplifier findings when multiple signals co-occur in the same package. This is where Shai-Hulud's score goes from "suspicious" to "automatic block."&lt;/p&gt;

&lt;p&gt;Four amplifiers would fire:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;worm_propagation (severity 5, critical)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fires when worm_behavior + (token_theft OR ci_secret_access) are both present. The correlator's evidence string is explicit:&lt;/p&gt;

&lt;p&gt;"This matches the Shai-Hulud worm pattern: steal tokens, publish malicious versions"&lt;/p&gt;

&lt;p&gt;This amplifier was added specifically because of Shai-Hulud. Confidence is capped at 0.95, and the critical: true flag means the score is floored at 100.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;secret_theft (severity 5, critical)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fires when ci_secret_access + network_exfil co-occur. The package reads secrets from the environment AND sends data over the network. Confidence derived from the weakest constituent + 0.15 boost, capped at 0.9.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;install_download_exec (severity 5)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fires when install_script + network_exfil co-occur. A lifecycle hook that makes network calls is the canonical dropper pattern. Capped at 0.85 confidence.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;bun_evasion_attack (severity 5, critical)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fires when bun_runtime_evasion + (network_exfil OR token_theft) co-occur. Using Bun-specific APIs alongside network exfiltration or credential theft indicates deliberate sandbox evasion.&lt;/p&gt;

&lt;p&gt;All four of these amplifier IDs appear in the UNCONDITIONAL_BLOCK_IDS list in the reporting engine. Any single one of them triggers an automatic block regardless of the numeric score.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Scan Output Would Look Like
&lt;/h2&gt;

&lt;p&gt;When Dependency Guardian runs in CI (as a GitHub Action, GitLab CI step, or any PR gate), the scan would produce output like this:&lt;/p&gt;

&lt;p&gt;Package: &lt;a href="mailto:colros@1.5.0"&gt;colros@1.5.0&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Score:   100 / 100&lt;br&gt;&lt;br&gt;
Verdict: BLOCK  &lt;/p&gt;

&lt;p&gt;Findings (7 base + 4 amplifiers):&lt;br&gt;&lt;br&gt;
  [sev 5] install_script      — Suspicious preinstall script&lt;br&gt;&lt;br&gt;
  [sev 5] ci_secret_access    — Reads NPM_TOKEN, GITHUB_TOKEN from environment&lt;br&gt;&lt;br&gt;
  [sev 5] token_theft         — Reads ~/.npmrc credential file&lt;br&gt;&lt;br&gt;
  [sev 5] network_exfil       — HTTP POST to api.github.com&lt;br&gt;&lt;br&gt;
  [sev 5] worm_behavior       — Executes npm publish programmatically&lt;br&gt;&lt;br&gt;
  [sev 4] child_process       — Shell command execution during install&lt;br&gt;&lt;br&gt;
  [sev 4] bun_runtime_evasion — Bun.spawn, Bun.file API usage  &lt;/p&gt;

&lt;p&gt;Amplifiers:&lt;br&gt;&lt;br&gt;
  [sev 5] worm_propagation     — Steals credentials AND self-publishes (CRITICAL)&lt;br&gt;&lt;br&gt;
  [sev 5] secret_theft         — CI secret access + network exfiltration (CRITICAL)&lt;br&gt;&lt;br&gt;
  [sev 5] install_download_exec — Install script with network access&lt;br&gt;&lt;br&gt;
  [sev 5] bun_evasion_attack   — Bun APIs used for sandbox evasion (CRITICAL)  &lt;/p&gt;

&lt;p&gt;The PR comment would display: BLOCK: Critical supply chain threat detected.&lt;/p&gt;

&lt;p&gt;The critical: true flag on worm_propagation alone is enough to floor the score at 100. Even if the base detector scores somehow summed to less than the block threshold, the critical flag overrides numeric scoring. The scoring engine enforces this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasCritical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;critical&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasCritical&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no configuration that overrides a critical finding. The PR is blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Key Insight
&lt;/h2&gt;

&lt;p&gt;Shai-Hulud was not sophisticated. It didn't use novel exploitation techniques. It didn't chain zero-days in the Node.js runtime. It was a preinstall script that read tokens, made HTTP calls, and ran npm publish. These are ordinary operations — but they are ordinary operations that a color utility package should never perform.&lt;/p&gt;

&lt;p&gt;CVE-based tools ask: "Is this package version in our vulnerability database?"&lt;/p&gt;

&lt;p&gt;Behavioral analysis asks: "Why is a CSS helper reading ~/.npmrc and posting to the GitHub API during installation?"&lt;/p&gt;

&lt;p&gt;That difference is why Dependency Guardian would have blocked Shai-Hulud on the first infected package, before the worm had a second host to spread to. No CVE required. No advisory needed. Just 26 detectors asking what the code actually does, and a correlator that recognizes when the answers form an attack pattern.&lt;/p&gt;

&lt;p&gt;The npm ecosystem will see more worms. The question is whether your CI pipeline asks the right questions before npm install finishes running.&lt;/p&gt;

&lt;p&gt;Dependency Guardian is free for up to 200 scans/month. Add it to your CI pipeline in 5 minutes: &lt;a href="https://westbayberry.com/docs" rel="noopener noreferrer"&gt;https://westbayberry.com/docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>javascript</category>
      <category>node</category>
      <category>security</category>
    </item>
  </channel>
</rss>
