惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
云风的 BLOG
云风的 BLOG
小众软件
小众软件
雷峰网
雷峰网
博客园 - 【当耐特】
V
V2EX
WordPress大学
WordPress大学
IT之家
IT之家
Last Week in AI
Last Week in AI
罗磊的独立博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
有赞技术团队
有赞技术团队
The Cloudflare Blog
Jina AI
Jina AI
博客园 - 司徒正美
阮一峰的网络日志
阮一峰的网络日志
博客园 - 聂微东
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Stop letting npm install run untrusted code on your machi...
Simon Kobler · 2026-05-17 · via DEV Community


npm install

Enter fullscreen mode Exit fullscreen mode

You type it dozens of times a day. You probably typed it this morning. And every time you did, you handed arbitrary code execution to every maintainer in your dependency tree — and every attacker who has phished one of them.

Over the last eight months, attackers have noticed. The Shai-Hulud family of worms has compromised hundreds of npm packages, created tens of thousands of malicious GitHub repos, and harvested thousands of developer secrets. The November 2025 wave alone hit 700+ packages, 27,000 malicious repos, and ~14,000 exposed secrets across 487 organizations — in under 48 hours.

I got tired of waiting for npm to fix this, so I built np-audit — a zero-dependency CLI that statically analyzes install scripts before npm executes them. This post is partly about why you need it, and mostly about how to use it.


The attack that keeps working

Every Shai-Hulud variant relies on the same three lines:

{
  "scripts": {
    "preinstall": "node setup.mjs"
  }
}

Enter fullscreen mode Exit fullscreen mode

That's it. The second npm install resolves a compromised version, setup.mjs runs before your code, before your tests, before any human looks at anything. Same user, same env vars, same network access as you.

The payload is always some flavor of the same recipe:

  1. Download a second runtime (Bun is the current favorite — bypasses Node-pattern detection).
  2. Run an obfuscated harvester that scans for GitHub tokens, npm credentials, AWS/Azure/GCP keys, Kubernetes service account tokens, Vault creds, browser-saved passwords, and CI runner secrets.
  3. Encrypt and exfiltrate by pushing to public GitHub repos via the GraphQL API — looks like normal git activity from the outside.
  4. Use any stolen GitHub PAT to inject malicious workflows into other repos the victim can write to. One dev becomes patient zero for their entire org.

A nice touch from the Mini Shai-Hulud variant:

if (locale.startsWith('ru') || lang.startsWith('ru')) {
  process.exit(0); // do nothing
}

Enter fullscreen mode Exit fullscreen mode

The same Russian-locale guardrail appears in three separate campaigns now attributed to TeamPCP.


This is not a vulnerability. This is the feature.

There is no CVE to patch here. npm preinstall, install, and postinstall scripts run automatically by design — that's how packages compile native addons, fetch platform binaries, set up build tooling. It's documented, intentional, and useful.

It's also a loaded gun in every Node project on Earth, and it has been getting fired regularly since 2018: event-stream, ua-parser-js, node-ipc, colors, faker, the Bitwarden CLI in April. The attackers aren't getting more sophisticated. They don't need to.

--ignore-scripts is the official advice. It also breaks bcrypt, node-sass, puppeteer, sharp, and half your toolchain. Nobody actually runs it in CI.

So we live with the gun pointed at us. Or we look at what the scripts actually do before we let them run.


Meet np-audit (npa)

np-audit is a static analyzer for npm lifecycle scripts. It downloads the tarballs npm is about to install, reads every preinstall / install / postinstall script, and flags the patterns that every documented supply chain attack has used:

  • eval() and new Function() calls
  • Obfuscator.io-style mangling (var _0x3f2a = [...])
  • High-entropy strings (encrypted/compressed payloads)
  • Hex escape density and String.fromCharCode() chains
  • Buffer.from(x, 'base64') followed by eval
  • Shell spawning via child_process
  • process.env access combined with outbound network calls

Each signal contributes to a score. Anything over a configurable threshold blocks the install. Zero runtime dependencies, pure Node built-ins, and — obviously — no install scripts of its own. The whole point of a supply chain auditor is that you can audit it in an afternoon.

Install

npm install -g np-audit

Enter fullscreen mode Exit fullscreen mode

Daily use

Just swap the verb:

npa install   # audit, then npm install
npa ci        # audit, then npm ci
npa scan      # audit only, no install

Enter fullscreen mode Exit fullscreen mode

If a package is suspicious, you get a clean report and a non-zero exit code — drop-in safe for CI:

✗ evil-pkg@1.0.0  postinstall: install.js  DANGER (score: 9)

Enter fullscreen mode Exit fullscreen mode

Interactive review for the paranoid

npa i --review

Enter fullscreen mode Exit fullscreen mode

Drops you into a TUI listing every install script in your tree. You decide one by one which ones get to run. Under the hood it's npm install --ignore-scripts followed by manual execution of only the scripts you approved — basically informed consent for lifecycle scripts.

Set and forget

npa alias --install

Enter fullscreen mode Exit fullscreen mode

Installs a shell hook so every npm install and npm ci you type is scanned first. Clean tree, npm proceeds. Suspicious tree, npm never runs.

$ npm install lodash
[npa] Scanning dependencies before npm install...
✔ No packages with install scripts found.
[npa] Scan passed. Running npm install...

Enter fullscreen mode Exit fullscreen mode

CI example

GitHub Actions:

- name: Install dependencies (audited)
  run: |
    npm install -g np-audit
    npa ci

Enter fullscreen mode Exit fullscreen mode

If a transitive dep gets compromised between your last green build and this one, the job fails before the malicious script touches your runner's env vars.


What it doesn't do

np-audit is not magic. A determined attacker writing clean, readable, plain-JavaScript malware can slip past a static heuristic check — that's a fundamental limit of static analysis.

The point isn't to be perfect. The point is to raise the cost from "drop in a preinstall and harvest 14,000 secrets in a weekend" to something that requires real effort. Every Shai-Hulud variant so far has leaned on heavy obfuscation precisely because the maintainers were trying to slip past human review. np-audit is human review at machine speed.


TL;DR

  • npm install runs untrusted code on your machine. This is by design.
  • The Shai-Hulud worms are exploiting that design at industrial scale and getting away with it.
  • --ignore-scripts breaks too much to be realistic.
  • np-audit looks at install scripts before they run, scores them, and blocks the obviously malicious ones.
npm install -g np-audit
npa ci

Enter fullscreen mode Exit fullscreen mode

Issues, PRs, and stars welcome: github.com/KoblerS/np-audit

Stay safe out there. And maybe read the next preinstall script before you let it read your ~/.aws/credentials.