Security toolkit for detecting supply chain vulnerabilities in NPM projects, designed to detect patterns from the major 2025-2026 npm supply chain attacks:
- CVE-2025-54313 (Scavenger malware - July 2025)
- Shai-Hulud worm (September 2025) and Shai-Hulud 2.0 "The Second Coming" (November 2025)
- Mini Shai-Hulud / TeamPCP family (April-June 2026): SAP CAP, Bitwarden CLI, TanStack (CVE-2026-45321), AntV, Miasma (Red Hat), Hades (PyPI)
- June-July 2026 campaigns by other actors reusing the same playbook: Mastra /
easy-day-js(Sapphire Sleet), Injective SDK, jscrambler / IronWorm, the aone-cli RAT cluster, Joyfill / PolinRider - ChainDrop (August 2026): the keyv / cacheable worm — 444 packages, 2259 versions, ~2 billion monthly installs, with its C2 list held in an Ethereum contract
- Flooding Dropper (August 2026): ~1000 dependency-confusion packages whose dropper runs on
require(), not on an install hook - AI coding-agent hijack (August 2026): daemons that take remote input and spawn
claude/codex/geminiwith--dangerously-skip-permissions
Detect and notify security vulnerabilities in NPM (and now PyPI) dependencies, including:
- 7965 compromised package versions across 3693 npm packages and 103 PyPI projects (Shai-Hulud v1/v2 + CVE-2025-54313 + the 2026 Mini Shai-Hulud waves + the June-August 2026 campaigns)
- Transitive dependencies: known-compromised packages are matched in
package-lock.json(v1/v2/v3 formats) and installednode_modules, not just directpackage.jsondependencies - Cross-ecosystem coverage: npm + PyPI (Hades wave), with Maven spillover documented
- Bun-runtime loaders used for EDR evasion (
setup.mjs,execution.js,bw1.js,_index.js,Math_Symbol.js, pinned Bun v1.3.13) - Import-time implants: the entry point (
main, orindex.js) of every installed package is analysed, because a payload that runs onrequire()never touches a lifecycle script - Dependency-confusion version inflation (
9999.0.0,1.0.999) in the lockfile and innode_modules - Blockchain dead-drop C2 (EtherHiding): Ethereum, Tron, Aptos, BSC and ICP canisters
- Malware persistence via agent/IDE config files (survives
npm uninstall) - OIDC / Trusted-Publishing abuse and GitHub commit-pinned tarball injection
- Shai-Hulud 2.0 Bun payloads (
setup_bun.js,bun_environment.js) - Typosquatting attempts
- Injected malicious code (DLL/SO files, obfuscated scripts)
- Suspicious installation scripts
- Worm-like/propagation behaviors
- C2 domain communication
- Cloud metadata endpoint (IMDS) access — AWS (including ECS
169.254.170.2), GCP, Azure, Alibaba, Tencent - Out-of-band exfiltration channels (Interactsh / OAST, Burp Collaborator, Pipedream, Telegram bot API, Zapier hooks, Serveo and ngrok tunnels)
npm-supply-chain-detector.py- Main Python detection scriptscan-npm-security.sh- Bash automation and monitoring scriptmalicious-patterns.json- Malicious patterns database (production; auto-synced from the IOC database byupdate-patterns.py)shai-hulud-iocs.json- Extended IOCs database (3693 npm packages / 7965 versions, 103 PyPI projects, 514 hashes, C2 domains, one section per campaign family) — the file loaded by the Python detectorupdate-patterns.py- Pattern database generator/updater (regeneratesshai-hulud-iocs.jsonand syncsmalicious-patterns.json). Regeneration is offline and deterministic: it touches no network and two runs produce byte-identical files.--refresh-remotere-scrapes the vendor page the November 2025 list originally came from, and shape-validates every package name and version before merging — a detection database must not be assembled from an unpinned third-party page.
# Clone or download scripts
chmod +x npm-supply-chain-detector.py scan-npm-security.sh
# Check prerequisites
./scan-npm-security.sh --help- Python 3.6+
- npm/node installed
- NPM project with package.json
# Scan current directory
python3 npm-supply-chain-detector.py
# Scan specific project
python3 npm-supply-chain-detector.py /path/to/project
# With detailed report
python3 npm-supply-chain-detector.py -v -o markdown -f report.md# Basic scan
./scan-npm-security.sh
# Continuous monitoring (scan every 5 minutes)
./scan-npm-security.sh -c -i 300
# Deep scan with save
./scan-npm-security.sh -d -f security-report.md -o markdown /project
# With webhook notifications (Slack, Discord, etc.)
./scan-npm-security.sh -w https://hooks.slack.com/services/XXX
# Quarantine suspicious packages (moves to .npm-quarantine/)
./scan-npm-security.sh --quarantine
# Restore quarantined packages if false positive
./.npm-quarantine/restore.sh-o, --output: Output format (json, text, markdown)-f, --file: Save report to file-v, --verbose: Verbose mode--webhook: Webhook URL for notifications
-c, --continuous: Continuous monitoring mode-i, --interval: Interval between scans (seconds)-d, --deep: Deep scan (includes npm audit, outdated packages)--quarantine: Move suspicious packages to quarantine folder--update-patterns: Rebuild the IOC database and pattern definitions (offline)
The --quarantine option automatically isolates compromised packages:
How it works:
- Scans for critical vulnerabilities
- Identifies compromised packages from scan results
- Moves packages from
node_modules/to.npm-quarantine/packages/ - Creates JSON manifest with metadata (versions, paths, timestamps)
- Generates automatic
restore.shscript
Directory structure created:
.npm-quarantine/
├── packages/ # Isolated packages
│ ├── package-name/
│ └── @scope_package-name/
├── logs/ # Scan logs
├── quarantine-manifest-*.json # Metadata
└── restore.sh # Restoration script
To restore (if false positive):
cd .npm-quarantine
./restore.sh # Interactive confirmation requiredMatched in three places: direct dependencies in package.json, the full dependency tree in package-lock.json (v1 nested tree and v2/v3 flat packages map), and packages actually installed under node_modules — so a compromised package pulled in three levels deep is still flagged.
CVE-2025-54313 (Scavenger - July 2025):
- eslint-config-prettier (8.10.1, 9.1.1, 10.1.6, 10.1.7)
- eslint-plugin-prettier (4.2.2, 4.2.3)
- synckit (0.11.9)
- @pkgr/core (0.2.8)
- napi-postinstall (0.3.1)
- got-fetch (5.1.11, 5.1.12)
- is (3.3.1, 5.0.0)
- @crowdstrike/node-exporter (0.2.2)
- @crowdstrike/threat-center (1.205.2)
- tailwind-toucan-base (5.0.2)
- Shai-Hulud 2.0 (Wiz, Nov 27 2025): ~470 packages / versions (e.g.
@asyncapi/*,@actbase/*,@accordproject/*,@antstackio/*, etc.) – seeshai-hulud-iocs.jsonfor the full list
September 2025 "qix" wave (chalk / debug):
The largest npm incident by reach. A phishing mail against one maintainer account poisoned a single version of each of ~20 micro-utilities that sit under almost every JavaScript toolchain, plus a tail of unrelated packages published from other stolen accounts in the same window — 107 packages in total, recorded at their exact versions:
chalk@5.6.1,debug@4.4.2,ansi-styles@6.2.2,strip-ansi@7.1.1,color-convert@3.1.1,color-name@2.0.1,supports-color@10.2.1,wrap-ansi@9.0.1,slice-ansi@7.1.1,ansi-regex@6.2.1,is-arrayish@0.3.3,error-ex@1.3.3,simple-swizzle@0.2.3,has-ansi@6.0.1,chalk-template@1.1.1,backslash@0.2.1,supports-hyperlinks@4.1.1,proto-tinker-wc@0.1.87duckdb@1.3.3,@duckdb/node-api@1.3.3,@duckdb/node-bindings@1.3.3,@duckdb/duckdb-wasm@1.29.2,prebid@10.9.1/10.9.2, and ~80 more (seeshai-hulud-iocs.json)
Only one version of each of these is malicious, which is why they are recorded as exact versions rather than wildcards — see the note on OVERBROAD_WILDCARDS in update-patterns.py.
Shai-Hulud worm (September 2025):
- CrowdStrike packages (@crowdstrike/*)
- @ctrl/tinycolor (4.1.1, 4.1.2)
- @nativescript-community/* packages
- @operato/* packages
- @things-factory/* packages
- Many others (see shai-hulud-iocs.json)
Shai-Hulud 2.0 (November 2025, Unit42 & Wiz)
- New payloads:
setup_bun.js,bun_environment.js - bun_environment.js hashes:
62ee164b9b306250c1172583f138c9614139264f889fa99614903c12755468d0,f099c5d9ec417d4445a0328ac0ada9cde79fc37410914103ae9c609cbc0ee068,cbb9bc5a8496243e02f3cc080efbe3e4a1430ba0671f2e43a202bf45b05479cd - setup_bun.js hash:
a3894003ad1d293ba96d77881ccd2071446dc3f65f434669b49b3da92421901a - GitHub exfiltration: repo description "Sha1-Hulud: The Second Coming"
- Possible destructive fallback (
rm -rf ~/$HOME)
Mini Shai-Hulud / TeamPCP family (April-June 2026)
Threat actor TeamPCP (aka UNC6780), who open-sourced their "Mini Shai-Hulud" malware, triggering a cascade of waves (and copycats). All of them use a Bun loader (EDR evasion) and exfiltrate via GitHub dead-drop repositories created on the victim's own account:
| Wave | Date | Ecosystem | Key packages |
|---|---|---|---|
| Bitwarden CLI ("The Third Coming") | 2026-04-22 | npm | @bitwarden/cli@2026.4.0 (via Checkmarx breach) |
| SAP CAP ("A Mini Shai-Hulud has Appeared") | 2026-04-29 | npm | mbt, @cap-js/sqlite, @cap-js/postgres, @cap-js/db-service |
| TanStack (CVE-2026-45321) | 2026-05-11 | npm + PyPI | 42 @tanstack/* pkgs, @mistralai/mistralai, @uipath/cli (OIDC/Trusted-Publishing hijack, valid SLSA provenance) |
| AntV ("Here We Go Again") | 2026-05-19 | npm | @antv/g2,g6,l7,s2, echarts-for-react, timeago.js, size-sensor (compromised atool maintainer account) |
| Miasma ("The Spreading Blight") | 2026-06-01 | npm | @redhat-cloud-services/* (via GitHub Actions OIDC) |
| Miasma "Phantom Gyp" | 2026-06-03 | npm | 68 packages (@vapi-ai/server-sdk, ai-sdk-ollama, autotel-*) executing through binding.gyp, which survives --ignore-scripts |
| Hades ("The End for the Damned") | 2026-06-07 | PyPI | 19 projects via *-setup.pth hook → Bun → _index.js |
| Hades wave 2 | 2026-06-08 | PyPI | 19 further projects; split-loader .pth that hunts sys.path for any _index.js, plus trojanized .abi3.so wheels |
| Miasma via hijacked GitHub Action | 2026-06-24 | Actions + npm | 23 tags of codfish/semantic-release-action rewritten; LeoPlatform/RStreams packages trojanized |
| Miasma downstream | 2026-06-26 | npm | 22 @immobiliarelabs/* Backstage plugin versions |
| Miasma RAT (AsyncAPI) | 2026-07-14 | npm | @asyncapi/* via a GitHub Actions pwn-request, published with valid OIDC/SLSA provenance |
The AntV entry deserves a note: it was previously recorded with 9 packages. Checking the OSSF/OpenSSF malicious-packages feed behind the OSV MAL-2026-* records puts the real scope at 324 packages / 645 versions, so the database understated that wave by a factor of about 35.
Two further families are tracked alongside these: IronWorm (2026-06-03, 37 npm packages via the compromised asteroiddao account — a Rust worm with an eBPF rootkit, which JFrog calls Shai-Hulud's "rustier cousin") and TrapDoor (2026-05-19, 34 attacker-created packages across npm, PyPI and crates.io that poison CLAUDE.md and .cursorrules).
The earliest wave covered is now TeamPCP's Trivy compromise (2026-02-27 to 03-23), which is the one worth knowing about even if you use none of the packages above: the actor force-pushed 76 of 77 aquasecurity/trivy-action tags to malicious commits, published backdoored aquasec/trivy Docker images, and hit the Checkmarx KICS and AST actions — the security tooling itself became the delivery vehicle. The companion CanisterWorm npm worm spread across 71 packages (@emilgroup/*, @opengov/*, @automagik/genie, pgserve…) using Internet Computer canisters as dead-drop C2.
PyPI coverage now reaches back before the npm waves. TeamPCP's earliest .pth-loader wave (2026-03-24) hit litellm 1.82.7/1.82.8 — a package with roughly 95M downloads a month — along with telnyx 4.87.1/4.87.2 and xinference 2.6.0-2.6.2. The loader (litellm_init.pth) runs on every Python interpreter start, not just on install. A separate typosquat cluster (2026-07-07) shipped 13 npm and 4 PyPI packages impersonating Paysafe, Skrill and Neteller; those were created by the attacker rather than hijacked, so every published version is treated as malicious.
Two standalone PyPI compromises using the same tradecraft are also covered: lightning (PyTorch Lightning) 2.6.2/2.6.3 on 2026-04-30, which hid a Bun infostealer in a _runtime directory that runs on import — note that the sibling pytorch-lightning distribution was not affected — and mrmustard 0.7.4 (XanaduAI) on 2026-07-24, an artifact-only injection where the poisoned wheel went straight to PyPI while the GitHub source stayed clean, so comparing against the repository would not have revealed it.
Novel TTPs covered: Bun v1.3.13 runtime (evasion), persistence via agent/IDE config files (.claude/.vscode/.cursor, survives npm uninstall), OIDC/Trusted-Publishing abuse, "living-off-trusted-host" exfiltration (api.anthropic.com/v1/api, filev2.getsession.org), and destructive wipe triggered on token invalidation.
June-July 2026 campaigns (other actors, same playbook)
The period after Hades is dominated by threat actors who are not Shai-Hulud but reuse its supply-chain techniques. They are tracked in their own npm_campaigns_h2_2026 section with separate attribution:
| Campaign | Date | Scope | What makes it notable |
|---|---|---|---|
Mastra AI / easy-day-js |
2026-06-17 | 144 npm packages | A dayjs typosquat silently added as a dependency across the whole @mastra scope; obfuscated postinstall drops the protocal.cjs RAT (Sapphire Sleet / BlueNoroff, DPRK) |
| Injective Labs SDK | 2026-07-08 | 18 @injectivelabs/* @ 1.20.21 |
No install hook at all — hooks PrivateKey.fromMnemonic/fromHex/generate and exfiltrates at runtime, disguised as trackKeyDerivation telemetry |
| jscrambler / IronWorm | 2026-07-11 | 5 versions + 4 plugins | Cross-platform Rust infostealer; 8.14.0-8.17.0 use a preinstall hook, 8.18.0/8.20.0 switch to import-time execution to defeat --ignore-scripts |
| aone-cli RAT cluster | 2026-07-28 | 19 packages | Capability split across a benign-looking dependency tree; vm sandbox escape; drops a private Bun runtime at ~/.real/.bin/bun; poisons .skills Python scripts of AI dev tools |
| Joyfill / PolinRider | 2026-07-28 | 2 packages (6 beta versions) | C2 resolved from Tron/Aptos/BSC blockchain transactions; implant baked into dist bundles at build time; worms into the global npm CLI, VS Code, Discord and GitHub Desktop |
Two of these run at import time rather than through a lifecycle script, so checking package.json hooks alone is no longer sufficient — the scanner hashes dist bundle files for exactly this reason.
August 2026: ChainDrop and Flooding Dropper
Tracked in their own npm_campaigns_aug_2026 section.
| Campaign | Date | Scope | What makes it notable |
|---|---|---|---|
| ChainDrop (keyv / cacheable) | 2026-08-04 | 444 npm packages / 2259 versions | Provenance was genuine; C2 list held in an Ethereum contract; plants hooks in .claude/settings.json and .vscode/tasks.json |
| Flooding Dropper | 2026-08-05 | ~1000 npm packages | Mass dependency confusion; the dropper fires on require(), so --ignore-scripts does not help |
ChainDrop started at 09:35 UTC with keyv@6.0.0. The attackers took over the maintainer's GitHub account rather than their npm account, pushed the payload straight to main and cut a release immediately — so every poisoned tarball reached npm with a valid provenance attestation signed by GitHub Actions. The signatures were not forged; the tarballs really were built by that repository, in that workflow, on that commit. Provenance attests build integrity, not source integrity, which is why signature verification alone would not have caught this wave. From there the worm used the stolen npm tokens (including OIDC trusted publishing) to republish everything within reach, crossing into twelve unrelated organisations in under four hours: @servicetitan, @onereach, @or-sdk, @ornikar, @qlik, @nebula.js, @deliveroo, @picsart and more. flat-cache and file-entry-cache ship inside ESLint, so the blast radius reached projects that never named either dependency.
Its C2 is the interesting part: rather than a hardcoded domain, the loader calls eth_call against contract 0xE1f2395…3103 on Ethereum mainnet and reads the current endpoint out of the return value (EtherHiding), falling back to a GitHub commit search. Taking down a domain does not stop it. The payload also sweeps AI assistant credentials (~/.claude/credentials.json, ~/.codex/auth.json, ~/.cursor/credentials.json) alongside the usual cloud and CI secrets.
Flooding Dropper is a different problem: roughly a thousand throwaway packages whose names shadow one organisation's internal modules (bigops-*, bnpl-*, devplatform-*, statist-browser-typed-client-*), published from disposable accounts mostly in the 35.x.y range so they win version resolution. The dropper is reached from index.js at import time, hides its hostnames by assembling them from string fragments at runtime (["oob-worker.cf100-416.w","orke","rs.","dev"].join("")), reaches child_process via require("child_"+"process"), stages its binary as a fake .NET diagnostic tool, and keeps a DNS-TXT channel in reserve for when HTTPS egress is blocked. Neither --ignore-scripts nor npm v12's default lifecycle blocking stops any of it.
Because of that second campaign, the scanner now analyses the entry point of every installed package (main, falling back to index.js), not only files with a known-suspicious name. It also flags version inflation — a dependency resolved to 9999.0.0 or 1.0.999 — as a warning, since that is the signature of a dependency-confusion win. CalVer versions such as 2024.1.0 are deliberately not matched.
August 2026 (2026-08-06/07): nine clusters in 48 hours
Not one campaign but a burst of unrelated activity, taken from the OSV bulk export — 114 npm packages and 7 PyPI projects. Three of them introduced techniques the scanner had no rule for:
| Cluster | Ecosystem | What makes it notable |
|---|---|---|
| AI coding-agent hijack | npm | remote-claude-daemon, agenthub-multiagent-mcp, @addai/*, @love-moon/conductor-cli, opencode-optimised-toolings, @ch4acko3/frontal-lobe — a daemon polls a WebSocket or Supabase RPC and spawns claude / codex / gemini / kimi / grok in a PTY with --dangerously-skip-permissions |
| jsonbin.io dead-drop | npm | helmet-pro (typosquat of helmet), vitest-preview-pro-all — postinstall fetches an api.jsonbin.io record and passes it to new Function('require', ...); the record is attacker-mutable, so the payload changes without republishing |
| Off-registry tarball | npm | commonweb-balance, connect-contingency, consumerweb-creditcollection — hollow packages at inflated 99.x versions whose only dependency is a direct tarball URL on a Google Cloud Storage bucket |
| RedShell implants | npm | streak-cache-map, streak-map-cache — a bundled Linux ELF chmod'd and spawned detached on require() |
| Baileys forks | npm | WhatsApp session relay to a host hidden in a String.fromCharCode array |
2026-08-flasq |
PyPI | fastapii, flasq, idnna, pydanticc typosquat fastapi/flask/idna/pydantic, override the setup.py install command and steal wallets |
The AI coding-agent hijack is a different shape from the instruction-file poisoning already tracked: nothing is written into CLAUDE.md or .cursorrules. The package runs the agent itself, with its safety prompt disabled, driven by whatever the remote peer sends — so the indicator is the permission flag, not the config file. One of them (agenthub-multiagent-mcp) also declared github.com/anthropics/agenthub as its repository, which is why the scanner now checks whether a package claiming a well-known vendor's repository is actually published under that vendor's scope.
The off-registry tarball cluster prompted a structural check rather than a pattern: npm accepts "dep": "https://host/pkg.tgz" and installs whatever bytes are served, lifecycle scripts included, entirely outside registry review. Any dependency or lockfile resolved entry pointing at a generic file host (Google Cloud Storage, S3, Azure Blob, R2, Aliyun OSS…) is reported critical; any other non-registry tarball URL is a warning. Registry hosts and private registries — Artifactory, Nexus, GitHub Packages, Verdaccio — are recognised and never reported.
- Levenshtein distance analysis
- Common substitutions (0→o, 1→i, etc.)
- Dash/underscore variations
- Credential exfiltration (AWS, npm, SSH, GitHub tokens)
- Remote code execution (curl|sh, eval)
- Obfuscation (base64, atob, XOR encryption)
- Suspicious network communication to C2 domains
- Self-propagation (worm patterns, npm publishing)
- Cloud metadata endpoint (IMDS) access
- CVE-2025-54313 specific: DLL/SO loading, logDiskSpace function
- Malicious preinstall/postinstall hooks
- Script download and execution
- npm token manipulation
- Windows DLL execution (rundll32, regsvr32)
- Malicious files: node-gyp.dll, loader.dll, version.dll
- Shai-Hulud bundle.js (7 SHA-256 hashes)
- Shai-Hulud 2.0 Bun payloads:
bun_environment.js(3 hashes),setup_bun.js(1 hash) - CVE-2025-54313 Scavenger (3 SHA-256 hashes)
- 2026 waves: Mini Shai-Hulud loaders, Miasma
binding.gypandindex.jsblobs, IronWorm native payloads (ELF/PE/Mach-O), Hades wheels and.pthhooks, and the June-July campaign payloads
The scanner also hashes every installed package.json and every package entry point, both of which it already opens for other checks — that is how the hollow dependency-confusion lures, which are identified by their manifest rather than by any code they ship, are caught. Hashes come from the OSV records' own evidence files wherever possible; the handful that describe a downloaded second stage cannot be corroborated that way and carry a vendor-reported (uncorroborated) label that appears in the finding itself.
Hashing is not limited to a fixed set of payload filenames any more. Because an exact hash match cannot produce a false positive, ordinary bundle names (index.cjs.js, index.es.js, index.esm.js) are hashed too — that is how the Joyfill implants, which live inside dist bundles rather than a dedicated dropper file, are caught. .cjs files are hashed as well.
Since npm v12 blocks lifecycle scripts by default, attackers moved elsewhere. Both routes are covered:
binding.gyp("Phantom Gyp") —node-gypevaluates GYP command expansion<!(cmd)at build time, which runs even undernpm install --ignore-scripts. Flagged when the expansion invokes an interpreter or chains shell commands, or when a target compiles nothing ("type": "none") yet still runs a command. A normal addon locating its headers with<!(node -p "require('node-addon-api').include")is not reported.- Import-time payloads — implants appended to
distbundles that run onrequire(). Matched through the detached-and-hiddenspawnshape,node -einvocation from a dependency, dynamicFunction()construction, whitespace padding, and IPFS/blockchain second-stage retrieval.
- Hijacked Actions — several Actions had their upstream tags force-pushed to attacker commits, including the security tooling itself:
aquasecurity/trivy-action(76 of 77 tags),aquasecurity/setup-trivy(all 7),Checkmarx/kics-github-action,Checkmarx/ast-github-actionandcodfish/semantic-release-action(23 tags). Any reference by tag is reported as critical, since a tag cannot be trusted once it has been force-pushed; a reference pinned to a full 40-character commit SHA is reported as a warning instead, because pinning is what makes it safe. The list is read from the IOC database, so covering a new one takes no code change. - AI instruction files —
CLAUDE.md,.cursorrules,AGENTS.md,.windsurfrules,.cursor/rules/*.mdcand.github/copilot-instructions.mdare scanned. TrapDoor and the Miasma waves append attacker instructions to these so the coding agent itself performs the exfiltration, often concealed with zero-width or bidirectional Unicode, and paired with a line telling the assistant not to mention it to the user.- node-gyp.dll: c68e42f416f482d43653f36cd14384270b54b68d6496a8e34ce887687de5b441
- Scavenger stage 2: 5bed39728e404838ecd679df65048abcb443f8c7a9484702a2ded60104b8c4a9
- install.js: 32d0dbdfef0e5520ba96a2673244267e204b94a49716ea13bf635fa9af6f66bf
- firebase.su (CVE-2025-54313)
- dieorsuffer.com (CVE-2025-54313)
- smartscreen-api.com (CVE-2025-54313)
- npnjs.com (typosquatting)
- webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7 (Shai-Hulud)
- t.m-kosche.com (Mini Shai-Hulud AntV C2)
- filev2.getsession.org, api.masscan.cloud, git-tanstack.com (TanStack wave)
- audit.checkmarx.cx (Bitwarden wave exfil)
- teams.onweblive.org, maskasd.com (Mastra wave); xemzqli2vu.ai-app.pub and the attacker-registered
aone-*.oss-cn-beijing.aliyuncs.comstaging buckets (aone-cli cluster) - npm-cache.com, js-mirror.com, pypi-get.com, awqhnjewqjkl.icu (ChainDrop); dl.wel1.ru (Flooding Dropper DNS-TXT fallback)
- Hardcoded public IP endpoints in URLs — loopback, link-local and RFC1918 ranges are excluded
A domain hit is reported as critical, so this list only contains hosts no honest dependency would contact. Legitimate infrastructure abused by these campaigns is matched by behaviour patterns instead, never blocked by hostname:
api.anthropic.com/v1/api— path-only detection (the host is legitimate, abused via a bogus path)registry.npmjs.org— the official npm registry; the worm's use of/-/whoamifor token validation is what gets flaggedapi.trongrid.io,fullnode.mainnet.aptoslabs.com,bsc-dataseed.binance.org,ip-api.com,temp.sh,check.torproject.org— real services; flagged only when they appear alongside dynamic code execution or bulk upload- Ethereum public RPC providers (
eth.llamarpc.com,eth.drpc.org,ethereum-rpc.publicnode.com,go.getblock.io, …) — flagged only when the response feedseval,new Function()or a spawn, which is the EtherHiding shape workers.dev(Cloudflare) andgithub.com/oven-sh/bun/releases— flagged on the specific subdomain shape and download behaviour, never on the hostapi.telegram.org,hooks.zapier.com— matched on the bot-API and catch-hook paths a dependency has no reason to call, not on the hostname
- Verification via
npm audit - Large JS file detection (>3MB)
- npm domain validation
{
"scan_date": "2025-01-22T10:30:00",
"project_path": "/path/to/project",
"findings": [
{
"severity": "critical",
"message": "KNOWN COMPROMISED PACKAGE",
"location": "package.json",
"details": {...}
}
],
"statistics": {
"total_findings": 5,
"critical": 2,
"warning": 3
}
}Formatted report with tables and structured sections.
Plain text report for CI/CD integration.
Critical findings can be sent to a webhook:
./scan-npm-security.sh -w https://your-webhook-urlPayload format:
{
"text": "NPM Security Alert: X critical vulnerabilities",
"findings": [...]
}- name: NPM Security Scan
run: |
python3 npm-supply-chain-detector.py . -o json -f scan-results.json
if [ $? -ne 0 ]; then
echo "Critical vulnerabilities found!"
exit 1
finpm-security-scan:
script:
- python3 npm-supply-chain-detector.py
artifacts:
reports:
paths:
- security-reports/stage('Security Scan') {
sh './scan-npm-security.sh -o json -f report.json'
}Modify malicious-patterns.json to:
- Add new compromised packages
- Define custom patterns
- Adjust notification thresholds
- Exclude false positives
For 24/7 monitoring:
# With systemd
sudo tee /etc/systemd/system/npm-security-monitor.service << EOF
[Unit]
Description=NPM Security Monitor
After=network.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/path/to/project
ExecStart=/path/to/scan-npm-security.sh -c -i 600 -w https://webhook.url
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable npm-security-monitor
sudo systemctl start npm-security-monitor-
Critical findings:
- Use quarantine mode:
./scan-npm-security.sh --quarantine - Immediately isolate compromised packages
- Regenerate all exposed tokens/secrets (npm, GitHub, AWS, SSH keys)
- Audit affected systems for credential exposure
- Check for unauthorized GitHub repositories named "Shai-Hulud"
- Review cloud metadata endpoint (IMDS) access logs
- Use quarantine mode:
-
CVE-2025-54313 specific:
- Check for malicious DLL/SO files (node-gyp.dll, loader.dll, etc.)
- Scan for connections to C2 domains (firebase.su, dieorsuffer.com, smartscreen-api.com)
- Windows systems: Review rundll32/regsvr32 execution logs
-
Warnings:
- Investigate suspicious patterns
- Verify package legitimacy before restoration
- Update to safe versions
- Review package maintainer changes
-
Post-incident:
- Replace with safe package versions:
npm install <package>@<safe-version> npm audit fixfor automatic fixes- Dependency review and lockfile verification
- Set up continuous monitoring (
-c -i 300) - Consider restoring from quarantine only after verification
- Replace with safe package versions:
- CrowdStrike NPM Attack 2025
- CVE-2025-54313
- CVE-2026-45321 (TanStack / Mini Shai-Hulud)
- Mini Shai-Hulud SAP CAP (StepSecurity)
- Bitwarden CLI hijack (JFrog)
- Shai-Hulud Miasma / Red Hat (Wiz)
- Shai-Hulud Hades PyPI wave (Socket)
- AntV ecosystem compromise (Socket)
June-July 2026 campaigns:
- Mastra / easy-day-js (JFrog) · Microsoft Security Blog
- Injective SDK backdoored (StepSecurity) · Socket
- IronWorm returns via jscrambler (JFrog) · Socket
- aone-cli RAT cluster targeting Alibaba developers (Socket)
- Joyfill beta releases compromised (Socket) · StepSecurity
Miasma sub-waves, IronWorm and TrapDoor:
- IronWorm, Shai-Hulud's "rustier cousin" (JFrog) · SafeDep IOC feed
- AsyncAPI compromised via GitHub Actions (StepSecurity) · Wiz
- TrapDoor crypto stealer poisoning AI instruction files (Socket)
- Hades/Miasma PyPI wave targeting bioinformatics and MCP developers (Socket)
- Miasma package list, machine-readable (Socket CSV)
- OSSF malicious-packages feed (source of the OSV
MAL-*records)
August 2026 — ChainDrop and Flooding Dropper:
-
ChainDrop: anatomy of a self-propagating worm (Microsoft Security Blog)
-
Inside the keyv compromise: preinstall malware, trusted provenance, IDE hooks (Snyk)
-
'Flooding Dropper' hits npm with ~850 malicious packages (Sonatype,
sonatype-2026-005660) -
OSV bulk export — used to pull the complete, exact affected-version list for both campaigns
- 7965 compromised package versions tracked across 3693 npm packages and 103 PyPI projects (updated 2026-08-07)
- 514 malware file hashes detected (Shai-Hulud v1/v2 + Scavenger + the 2026 Mini Shai-Hulud, Miasma, IronWorm, ChainDrop, Flooding Dropper and the 2026-08-06/07 clusters)
- Multiple attack campaigns covered: CVE-2025-54313, Shai-Hulud v1/v2, the 2026 Mini Shai-Hulud / TeamPCP family (CVE-2026-45321), the June-July 2026 campaigns run by other actors, and the August 2026 ChainDrop and Flooding Dropper waves
- Scanner designed to minimize false positives. A domain match is reported as critical, so the list holds only hosts no honest dependency would contact;
registry.npmjs.org, the blockchain RPC endpoints,temp.sh,check.torproject.organdapi.anthropic.comare all matched by behaviour instead. Verified against legitimate.claude/.vscodeconfigs, realnode-addon-apibuild files, lockfiles withresolvedURLs, ordinary development addresses (127.0.0.1,0.0.0.0, RFC1918), CalVer version numbers, an ACME client resolving DNS TXT records, and an optional-binary installer of the download/chmod 0755/spawn kind that esbuild and sharp use - Patterns based on real observed attacks (July 2025 - July 2026)
- Regular update of malicious patterns recommended
- Compatible with all standard NPM projects
- Quarantine feature allows safe isolation with restoration option
- Does not replace thorough manual analysis
- May not detect zero-day attacks
- Performance dependent on project size
- Requires read permissions on node_modules
Detection rules are measured against real code, not only against synthetic samples. Two corpora are used:
- 44 popular, uncompromised npm packages downloaded straight from the registry (express, lodash, axios, esbuild, sharp, webpack, typescript, eslint, ws, undici, acme-client, bcrypt…). The expected result is zero findings.
- Fixtures pairing every rule with the legitimate construct it could be confused with: an optional-binary installer of the download/
chmod 0755/spawn kind, anode-addon-apibuild file,python3 -cheader lookups, an ACME client resolving DNS TXT records, CalVer version numbers, the clean neighbouring releases ofchalkanddebug, and ordinary development addresses.
That exercise found and removed four rules that fired on ordinary code: an ungrouped alternation that collapsed to the bare word spawn, another that collapsed to net.connect, npm publish --access public (the standard release command for any scoped package), and process.env.<X> followed by fetch( — which is what every API SDK does, and which matched almost any minified bundle. Each was replaced by the narrower shape that carries the actual signal: the environment being serialised whole and sent, npm publish invoked from code, a connection to a hardcoded public address, and the scanner being run or downloaded rather than merely mentioned.
To add new patterns or compromised packages:
-
Update pattern files:
- Edit
update-patterns.pyto add new packages/patterns - Run
python3 update-patterns.pyto regenerateshai-hulud-iocs.jsonand syncmalicious-patterns.json(packages, code patterns, C2 domains) - Avoid editing the JSON files by hand — manual edits are overwritten on the next regeneration
- Edit
-
Test changes:
python3 npm-supply-chain-detector.py -v test-project/
-
Update documentation:
- Update statistics in
README.md
- Update statistics in