BYBOWU > Blog > Web development

NPM Supply Chain Attack: Your 7‑Day Fix Plan

blog hero image
If your JavaScript builds started acting weird this week, you’re not alone. A second wave of the Shai‑Hulud npm supply chain attack was detected on November 24, and it’s exploiting the same install‑script paths most teams ignore. Add to that npm’s token changes earlier in November—classic tokens were revoked on November 19 and write‑scoped granular tokens now enforce 2FA—and many orgs are exposed. Here’s a pragmatic, battle‑tested plan to contain the blast radius, rotate t...
📅
Published
Nov 27, 2025
🏷️
Category
Web development
⏱️
Read Time
11 min

The latest npm supply chain attack isn’t abstract risk; it’s live‑fire. A second wave of the Shai‑Hulud campaign was detected on November 24, 2025, riding install lifecycle scripts to harvest developer credentials and CI/CD secrets. In the same month, npm finalized token changes—classic tokens were disabled earlier and permanently revoked on November 19—forcing teams onto granular access tokens with 2FA and shorter lifetimes. Put those together and you get a perfect storm: compromised packages plus brittle credential hygiene. This guide is the seven‑day, no‑nonsense plan I’d hand my own team.

Illustration of CI/CD pipeline highlighting risky npm install stages

What actually changed this month—and why it matters

Let’s anchor on facts and dates so you can brief leadership and move. First, security firms flagged a renewed Shai‑Hulud wave on November 24 that uses preinstall/install/postinstall hooks to run credential‑stealing payloads during package installation. New filenames to watch for in compromised tarballs: setup_bun.js and bun_environment.js. The playbook is depressingly familiar: compromise a maintainer, publish booby‑trapped versions, and wait for someone to run npm install in a privileged environment.

Second, npm’s token model changed this month. As of November 5, creation of classic tokens was shut off; on November 19, remaining classic tokens were revoked. Granular access tokens now enforce 2FA for write scopes by default, offer a CI bypass flag (opt‑out by default), and cap token lifetime to 90 days. If your org didn’t rotate or rewrite automation before the cutoff, your pipeline likely sprinkled long‑lived secrets everywhere—or it broke and someone hot‑patched in a risky token. Both are dangerous when attacks like this hit.

How Shai‑Hulud moves through your pipeline

Here’s the thing: the malware doesn’t need root. It just needs your build to run scripts. A typical path looks like this:

1) You add or update a dependency. 2) During npm install (or pnpm/yarn equivalents), a lifecycle script triggers. 3) The script enumerates files for secrets (env vars, .npmrc, shell history, CI configs), scrapes tokens and cloud creds, then exfiltrates. 4) With your GitHub PAT or npm token, the actor pushes trojanized packages or touches other repos to propagate. Some variants attempt destructive cleanup if persistence fails.

Why this lands hard: many teams still cache node_modules, run installs with network egress wide open, and let scripts execute in CI on the default runner image with privileged credentials in environment variables. That’s convenience stacking up into compromise.

7‑day incident‑to‑hardening plan

You don’t need a war room for a month. You need a structured week. Print this, assign names, and move.

Day 0–1: Contain and stop the bleeding

• Freeze deploys for affected services and switch builds to ignore scripts immediately. For npm: set env NPM_CONFIG_IGNORE_SCRIPTS=true or run npm ci --ignore-scripts (same for npm install). pnpm: pnpm install --ignore-scripts. Yarn Classic: yarn install --ignore-scripts. Yarn Berry: add enableScripts: false in .yarnrc.yml.

• Lock egress. In CI, deny outbound traffic except https://registry.npmjs.org and your artifact proxy. If you can’t enforce egress controls, run dependency installation in an isolated job with no cloud credentials, no GitHub PAT, and no secrets in environment variables.

• Clear caches and artifacts. Purge CI caches for node_modules, ~/.npm, ~/.pnpm-store, and any private registry caches. Run npm cache clean --force. Delete stale artifacts that may contain tainted packages.

• Invalidate stolen secrets. Rotate npm tokens, GitHub PATs, and any cloud keys used in builds. If your org is on GitHub, kill unused PATs and move automation to OIDC‑based federation for AWS/GCP/Azure so ephemeral credentials replace long‑lived secrets.

• Snapshot for forensics. Save lockfiles, package manifests, CI logs, and runner images from the last known‑good and first bad build.

Day 2: Inventory and identify compromised packages

• Diff your lockfiles across the time window. Look for suspicious version bumps, new minor versions on leaf dependencies, and packages with install hooks. Grep your dependency tree for lifecycle scripts: grep -R "\"install\"\|\"preinstall\"\|\"postinstall\"" node_modules/*/package.json.

• Flag remote dynamic dependencies. No dependency should point to http:// or arbitrary URLs. If you find any, quarantine that service until replaced.

• Roll back to known‑good. Pin exact versions in package-lock.json/pnpm-lock.yaml/yarn.lock that precede November 24 for suspicious packages. Reinstall with --ignore-scripts and confirm reproducible builds with a clean container.

Day 3: Rotate credentials the right way

• Replace any remaining classic npm tokens with granular tokens. Scope them to the smallest possible surface (single package, read‑only when feasible). Set expirations under 90 days and diarize rotation.

• Enforce 2FA for org members and publishing. For CI, use the “bypass 2FA” option only on tokens dedicated to non‑interactive automation and lock them to a single machine user.

• Migrate cloud auth in CI to OIDC so runners never hold static cloud keys. For GitHub Actions, use aws-actions/configure-aws-credentials with role trust on your AWS account.

• Switch GitHub from PATs to fine‑grained PATs with repo‑scoped permissions where automation still needs them. Better yet, use GitHub Apps with installation tokens.

Day 4: Sandbox builds and remove foot‑guns

• Split your pipeline: one job performs dependency resolution in a sandboxed container with --ignore-scripts, writes a lockfile and a vendorized tarball repo (or internal registry publish), and signs the artifact. A subsequent job consumes that artifact in a separate, restricted environment with no network access during build.

• Run installs with lockfile enforcement. npm: npm ci (which fails on mismatch). Yarn: --immutable. pnpm: --frozen-lockfile. This prevents surprise version shifts.

• Block lifecycle scripts by policy. Keep --ignore-scripts defaulted “on” in CI. If a package truly needs a postinstall (native modules, for example), explicitly allowlist that package by name and version in pipeline config, then review any update as a code change.

• Container hygiene. Use minimal images, drop root, mount workspaces read‑only where possible, and rebuild runners regularly to evict any persistence a script might achieve.

Day 5: Add provenance and tamper signals

• Publish with provenance. If you maintain packages, enable npm package provenance in your release workflow so consumers get cryptographic proof that a package was built from a specific repo and commit.

• Sign what you ship. Generate SBOMs (CycloneDX or SPDX) during builds and attach them to releases. Store hashes for lockfiles and vendor bundles; verify before deploy.

• Turn on secret scanning and push protection for your org’s repos so stray tokens never make it to git in the first place.

Day 6: Principle of least privilege—everywhere

• Granular npm tokens only. One per automation use case, scoped to the minimum resources, with 90‑day or shorter lifetimes and alerts on expiration. Rotate on a schedule, not in a panic.

• GitHub permissions. Protect main, require reviews on dependency updates, and use CODEOWNERS to route package changes to a security‑aware reviewer.

• Registry policy. For internal apps, prefer a private mirror (Artifactory, Verdaccio, GitHub Packages). Sync only allowlisted packages and versions that have passed your checks.

Day 7: Monitor, drill, and close the loop

• Add detection. Log egress from CI jobs, alert on unexpected destinations, and baseline normal package install behavior. Flag when a build attempts to access browser storage paths, SSH config, or cloud CLI directories.

• Run the drill. Recreate the last week’s incident in a sandbox. Measure how long it takes your team to detect, revoke, rebuild, and restore.

• Debrief and document. Capture what you changed, the exceptions you approved, and the owner for each control so you’re not back here in six months.

Photo of developer terminal focusing on npm ignore-scripts command

People also ask

What’s the safest way to run npm in CI right now?

Use npm ci --ignore-scripts in a network‑restricted job that has zero long‑lived credentials. Enforce the lockfile, vendor the dependency set as an artifact or through a private registry, and consume it in a second job with the network off.

Should I disable postinstall scripts permanently?

In CI, yes—make it the default. If you truly depend on a package that needs an install hook (for native compilation), allowlist that package+version and isolate that step. On developer machines, keep scripts on but run with least privilege and a modern Node toolchain.

How do I check if a package ran a lifecycle script?

Set npm_config_loglevel=verbose or pass --verbose to see script execution in logs. You can also scan node_modules/*/package.json for scripts keys with install/preinstall/postinstall. If anything surprises you, pin and review the package source before updating.

Data points your execs will ask about

• November 24, 2025: second wave of Shai‑Hulud observed using new payload files and install hooks.

• November 5, 2025: npm stopped issuing classic tokens.

• November 19, 2025: remaining classic tokens revoked; granular tokens enforce 2FA for write scopes and max 90‑day lifetimes; CI 2FA bypass available but off by default.

• Impact pattern: targeted package takeovers and repo spread via harvested GitHub/npm credentials; typical blast radius includes CI caches, private registries, and sibling repos.

Practical checklists you can use today

Immediate containment checklist

• Set NPM_CONFIG_IGNORE_SCRIPTS=true in CI and rebuild from a clean container.
• Purge node_modules caches and ~/.npm or ~/.pnpm-store.
• Lock CI egress to registry only.
• Rotate npm tokens, GitHub PATs, and any cloud creds used in builds.
• Roll back suspect packages to versions prior to the November 24 window and reinstall with scripts disabled.

7‑day hardening checklist

• Enforce lockfile‑only installs (npm ci / --frozen-lockfile / --immutable).
• Always run installs with --ignore-scripts in CI; allowlist rare exceptions.
• Move CI cloud auth to OIDC; remove static keys from secrets stores.
• Replace classic tokens with granular, least‑privileged tokens; set expirations ≤90 days.
• Add provenance to published packages and sign SBOMs.
• Monitor egress and add anomaly alerts for secret scraping behaviors.

Let’s get practical: reference pipeline

A minimal two‑stage GitHub Actions pattern that aligns with everything above:

Stage 1 (Resolve): Clean containernpm ci --ignore-scripts → produce node_modules.tar.zst or publish to a private registry → generate SBOM → sign artifact → upload.

Stage 2 (Build/Test): New clean container → download artifact → network off → build and test → no tokens in env.

Pair this with spend controls if you’re raising cache limits or moving storage classes in your CI platform. If you haven’t set those up yet, our write‑up on new Actions billing controls includes concrete guardrails; see the playbook on standardizing GitHub Actions billing and cache budgets.

Risks, limitations, and edge cases

• Some native modules genuinely need install hooks. Don’t ban them blindly—segregate builds that require hooks, document the allowlist, and monitor those jobs more tightly.

• Private registries won’t save you if you mirror blindly. You must curate and promote packages through an internal quarantine stage.

• Legacy apps with floating semver ranges are magnets for surprise updates. If you can’t pin everything, at least pin leaf dependencies known to be risky and add alerts on transitive upgrades.

What to do next

• Ship the Day 0–1 actions today. They’re low‑risk and high‑impact.
• Assign an owner to finish the seven‑day plan and report status daily.
• Add a monthly rotation for granular tokens and a quarterly drill for supply chain incidents.
• If you need a rapid review of your pipeline or help standing up a private mirror with policy, our team can help. Start with our security hardening and DevOps services, browse relevant case studies in the portfolio, and reach out via contacts. For ongoing updates like this, subscribe to the Bybowu blog.

Zooming out: this isn’t the last npm supply chain attack

The volume of JavaScript dependencies in modern apps makes perfect targets—tens of direct packages, hundreds of transitives, frequent updates, and a habit of trusting install hooks. The fix is cultural plus technical: treat dependency updates like code changes, treat credentials as radioactive, and treat CI as a zero‑trust environment. Do that, and the next wave becomes a noisy inconvenience, not a week‑long outage.

Diagram of secure two-stage CI pipeline for npm projects
Written by Viktoria Sulzhyk · BYBOWU
3,341 views

Work with a Phoenix-based web & app team

If this article resonated with your goals, our Phoenix, AZ team can help turn it into a real project for your business.

Explore Phoenix Web & App Services Get a Free Phoenix Web Development Quote

Get in Touch

Ready to start your next project? Let's discuss how we can help bring your vision to life

Email Us

[email protected]

We typically respond within 5 minutes – 4 hours (America/Phoenix time), wherever you are

Call Us

+1 (602) 748-9530

Available Mon–Fri, 9AM–6PM (America/Phoenix)

Live Chat

Start a conversation

Get instant answers

Visit Us

Phoenix, AZ / Spain / Ukraine

Digital Innovation Hub

Send us a message

Tell us about your project and we'll get back to you from Phoenix HQ within a few business hours. You can also ask for a free website/app audit.

💻
🎯
🚀
💎
🔥