跳到正文
DEV Community· Shubham Chaudhary·· 3 小時前AI 評分67

node_modules 是攻擊面:2026 年 npm 實用安全檢查清單

Your node_modules Is an Attack Surface: A Practical npm Security Checklist for 2026

AI 導讀

Shubham Chaudhary 整理 2026 年 npm 供應鏈攻擊案例,並提出一份約 30 分鐘可套用於專案的安全檢查清單。清單涵蓋預設停用安裝腳本、提交及審查 lockfile、設定新版本冷卻期、驗證簽章與 provenance,以及限制 CI/CD 權限和密鑰暴露。作者提醒,provenance 並非安全保證,受入侵的建置流程仍可能產生帶有有效 provenance 的惡意套件。

正文

On March 31, 2026, someone hijacked the npm account of axios's lead maintainer. Axios is an HTTP client with over 100 million weekly downloads. The attacker skipped the project's CI pipeline and published two malicious versions directly from the npm CLI.

Those versions didn't touch axios's own code. They added one new dependency, plain-crypto-js, which existed only to run a postinstall script. That script contacted a command-and-control server and dropped a remote access trojan on the machine.

All of this happened during npm install, before any application code ran.

The malicious versions were live for about four hours. Any fresh install or auto-update in that window could have pulled them.

💀 You didn't install malware. You ran npm install.

This post covers what happened in 2026, how these attacks work, and a checklist you can apply to your project in about 30 minutes. 👇


📅 2026 So Far: A Quick Recap

Axios wasn't a one-off. Here are some of the year's major incidents:

Date Target How
Mar 31 axios Maintainer account hijacked; malicious dependency with a postinstall script
Apr 22 @bitwarden/cli Preinstall script downloaded the Bun runtime and ran a credential stealer
Apr 29 SAP CAP packages (@cap-js/*, mbt) "Mini Shai-Hulud" worm, preinstall hook
May 11 @tanstack/* and others Abused a pull_request_target workflow and poisoned the GitHub Actions cache to steal an OIDC token; published with valid provenance
May 19 @antv/* Compromised maintainer account; ~639 malicious versions across 323 packages in about an hour
Jun 1 @redhat-cloud-services/* Compromised employee GitHub account pushed commits that bypassed code review
Jun 3 57+ packages binding.gyp campaign; 286+ malicious versions in under two hours
Jul 14 AsyncAPI packages Commits pushed to unprotected pre-release branches; CI exposed NPM_TOKEN

📈 The pattern is clear: attacks are faster, automated, and increasingly target the CI pipeline instead of just individual developers.


🧠 How These Attacks Actually Work

Most incidents combine a few of these techniques.

1. 🔓 Account Takeover

A maintainer gets phished or has a token leaked, and the attacker publishes a "new version" of a trusted package.

Your ^1.2.0 range happily accepts it.

2. 📜 Install Scripts

npm runs preinstall, install, and postinstall scripts automatically.

That means arbitrary code on your machine or CI runner, with access to your environment variables, SSH keys, and cloud credentials.

3. 🪱 Self-Replication (Worms)

The Shai-Hulud family steals npm tokens and GitHub tokens, then uses them to publish infected versions of other packages the victim maintains.

One compromised developer becomes dozens of compromised packages.

4. 🏭 CI/CD Compromise

The TanStack attack needed no stolen password.

The attacker abused a GitHub Actions workflow to extract a publishing token from the runner. Because the package was built by the real pipeline, it carried valid provenance.

5. 🎭 Typosquatting and Dependency Confusion

Look-alike package names (reacct-dom), or a public package with the same name as your private one.

🕵️ What Do Attackers Steal?

  • 🔑 npm and GitHub tokens
  • ☁️ Cloud credentials (AWS, GCP, Azure)
  • ☸️ Kubernetes tokens
  • 🗝️ SSH keys
  • ⚙️ CI/CD secrets
  • 📄 .env contents
  • 🔐 Password manager data
  • 🤖 AI tool configuration files

Some of the 2026 attack waves targeted credentials and configuration data beyond traditional development secrets.


📋 The Checklist

Here are practical steps you can take to reduce your exposure.

1. 💻 Local Development

🔒 Commit Your Lockfile, and Review It

package-lock.json (or pnpm-lock.yaml / yarn.lock) pins exact versions of your whole dependency tree.

Treat lockfile changes in a PR as code changes.

A new, unfamiliar transitive dependency (like plain-crypto-js) is a red flag. 🚩

🚫 Turn Off Install Scripts by Default

Most attacks this year relied on preinstall or postinstall.

Disable them:

.npmrc

ignore-scripts=true

Some packages genuinely need build scripts (native modules like sharp or bcrypt).

Run their scripts explicitly after install:

npm rebuild sharp --ignore-scripts=false

If you use pnpm, it already doesn't run dependency build scripts by default (since v10). Approve only the ones you trust with:

pnpm approve-builds

Bun also skips lifecycle scripts unless a package is in trustedDependencies.

⏳ Add a Release Cooldown

Malicious versions are usually caught within hours.

The axios versions were flagged about six minutes after publishing and contained within roughly four hours.

If your package manager refuses versions newer than a day or two, you skip most of that window.

All major package managers now support this, but each uses a different unit:

📦 npm 11.10+ (.npmrc) — value in DAYS

min-release-age=2

📦 pnpm 10.16+ (pnpm-workspace.yaml) — value in MINUTES

# pnpm 11 defaults to 1440 (24 hours)
minimumReleaseAge: 2880

📦 Yarn 4.10+ (.yarnrc.yml) — value in MINUTES

npmMinimalAgeGate: 2880

📦 Bun (bunfig.toml) — value in SECONDS

[install]
minimumReleaseAge = 172800

Need an urgent hotfix? 🔥

Override for one install, e.g.:

npm i --min-release-age 0 package@version

🎯 Be Deliberate With Version Ranges

^ and ~ let new versions in automatically.

For critical or high-privilege dependencies, consider exact versions and upgrade on purpose.


2. 🔍 Auditing

🩺 Run npm audit Regularly

It catches known vulnerabilities, not fresh malware, but it's still a baseline.

✍️ Verify Signatures and Provenance

npm audit signatures

This checks registry signatures and provenance attestations for your installed packages.

The axios malicious releases were missing the GitHub Actions provenance that legitimate releases had, which was a key signal.

🚨 But don't treat provenance as proof of safety.

The TanStack and Red Hat packages had valid provenance because the attackers compromised the real build pipeline.

📡 Add a Dependency Scanner

Tools like Socket, Snyk, or GitHub's Dependabot alerts watch for suspicious behavior (new install scripts, network calls, obfuscated code) and known-malicious packages.

Socket flagged the axios release within minutes.

🤖 Make Your Update Bots Wait Too

Dependabot and Renovate can open PRs, and trigger CI installs, before a human looks.

Give them a cooldown:

.github/dependabot.yml

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    cooldown:
      default-days: 2

renovate.json

{
  "minimumReleaseAge": "2 days"
}

Both still let security updates through without the delay.


3. 🏗️ CI/CD Security

🧊 Use Deterministic Installs

Use the appropriate install command for your package manager:

npm ci                              # npm
pnpm install --frozen-lockfile      # pnpm
yarn install --immutable            # yarn

These fail if the lockfile and package.json disagree, instead of silently resolving new versions.

🙈 Keep Secrets Away From the Install Step

Don't expose deploy keys or cloud credentials as environment variables during npm ci.

Inject them only into the steps that need them.

⏱️ Use Short-Lived, Least-Privilege Tokens

Avoid long-lived npm tokens in CI.

If you publish packages, use npm Trusted Publishing (OIDC) and enable 2FA on your npm account.

🧱 Harden GitHub Actions

  • ⚡ Be very careful with pull_request_target. It runs with your repo's secrets, and checking out PR code inside it is how the TanStack attack started.
  • 📌 Pin third-party actions to a full commit SHA, not a tag.
  • 🌿 Protect all branches that can trigger publishing, including next or pre-release branches (the AsyncAPI attack used one).

🌐 Restrict Outbound Network Access

Restrict outbound network access from CI runners where you can, so a payload can't reach its command-and-control server.


4. 🚑 Have a Response Plan

If a package you use is compromised:

  1. 🔎 Identify exposure. Check whether any lockfile in your projects resolved to an affected version, and when your machines or CI ran an install.
  2. 🧯 Isolate before rotating. Some 2026 payloads (the TanStack wave) installed a background service that deletes your home directory if it detects the stolen GitHub token was revoked. Clean or reimage affected machines first, then rotate.
  3. 🔄 Rotate everything the install could see: npm tokens, GitHub tokens, cloud keys, SSH keys, and CI/CD secrets.
  4. 🧹 Rebuild from a clean lockfile pinned to a known-good version.
  5. 🎣 Hunt for unexpected repos, workflows, or package versions published from your accounts.

🧰 Putting It Together: A Hardened Starter Setup

Here's a minimal setup you can drop into a project today.

⚙️ .npmrc

ignore-scripts=true
min-release-age=2
save-exact=true

🔁 .github/workflows/ci.yml (Relevant Parts)

name: CI

on:
  pull_request:        # not pull_request_target
  push:
    branches: [main]

permissions:
  contents: read       # least privilege by default

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<full-commit-sha>
      - uses: actions/setup-node@<full-commit-sha>
        with:
          node-version: 22
          cache: npm

      - name: Install (no secrets in this step)
        run: npm ci

      - name: Verify signatures
        run: npm audit signatures

      - name: Rebuild trusted native deps
        run: npm rebuild sharp --ignore-scripts=false

      - name: Build & test
        run: npm run build && npm test

It isn't bulletproof. Nothing is.

But it closes the paths that most 2026 attacks actually used: install scripts, brand-new malicious versions, silent version drift, and over-privileged CI. 💪


🏁 Key Takeaways

  • 🚪 Install scripts are the #1 entry point. Disable them by default and allowlist the few you need.
  • ⌛ Time is your cheapest defense. A 1–2 day cooldown would have skipped most of this year's malicious releases.
  • 🔐 Lockfiles plus npm ci stop silent changes to your dependency tree.
  • 🧾 Provenance helps, but it isn't proof. Compromised pipelines can produce validly signed malware.
  • 🎯 Your CI is a target. Least-privilege tokens, careful workflow triggers, and SHA-pinned actions matter.

The next time you run npm install, remember: you're not just installing code. You're trusting every package in your dependency tree to execute safely.

🛡️ Secure your dependencies before attackers turn them into your weakest link.

來源:DEV Community · dev.to