Another npm supply chain attack is ripping through the JavaScript ecosystem—and it’s a meaner sequel. On November 24, 2025 (UTC), a second wave of the Shai‑Hulud campaign began publishing compromised packages, targeting popular orgs and exfiltrating developer and CI/CD secrets within minutes of install. Multiple security teams and maintainers confirmed new indicators: the malware now executes during preinstall, drops files named setup_bun.js and bun_environment.js, and sprays stolen secrets into attacker‑labeled GitHub repos. (orca.security)
That shift matters. Preinstall runs earlier in the lifecycle, so more environments—local and automated—execute it. If you install dependencies in CI, or you have maintainers who publish from laptops, assume exposure until you prove otherwise. Here’s what changed, who’s at risk, and a field‑tested checklist to triage in the next 48 hours—then harden your pipelines for the rest of the quarter.
What just happened—and who got hit
Within hours, maintainers and vendors flagged compromises across several well‑known namespaces, including AsyncAPI, Postman, PostHog, Zapier, and ENS. Postman, for example, reported and unpublished infected versions in the early U.S. morning of November 24 (PT). Community telemetry and threat‑intel writeups describe tens of thousands of public repos created by the malware’s exfil routines, with some estimates citing 19k–25k+ repositories touched as the wave propagated. (blog.postman.com)
Expect the list of affected packages to evolve. This is a campaign, not a single mispublish. Treat any install runs during November 21–24 as suspect until you verify your dependency graph, caches, and build images are clean. (wiz.io)
What changed in Shai‑Hulud 2.0
Compared to September’s outbreak, the new variant ups the blast radius and persistence. The notable technical changes:
- Lifecycle shift: execution moved from postinstall to preinstall, which many teams allow even in minimal CI steps.
- Bun‑based loader: packages include
setup_bun.jsto install or locate the Bun runtime silently, then execute an obfuscated 10MB payload inbun_environment.jswith output suppressed. (socket.dev) - Credential sweep and exfil: the payload hunts for npm, GitHub, and cloud (AWS/GCP/Azure) credentials, then pushes them to public repos whose descriptions reference “Sha1‑Hulud: The Second Coming.” (esentire.com)
- GitHub persistence: some analyses show malicious workflows and even self‑hosted runner registrations (e.g., names like
SHA1HULUD) to maintain ongoing access. (scout.docker.com)
The net effect: once a developer or CI job touches a tainted package, secrets can leak into public repos in minutes, and the malware can attempt to boomerang back into your org through new workflows or compromised tokens. (wiz.io)
What is an npm supply chain attack, really?
In practice, an npm supply chain attack is any compromise delivered via a dependency you install or publish—malicious code piggybacks on your normal tooling. In Shai‑Hulud’s case, attackers hijack maintainer accounts, push trojanized releases, and rely on your build to execute lifecycle scripts that do the rest: steal secrets, plant backdoors, and republish from your accounts to spread further. Modern CI makes this fast because automation runs with powerful credentials and default‑open egress.
The 48‑hour triage checklist
Let’s get practical. Here’s a tight, two‑day response plan I’ve used with teams this week. Assign an incident lead, record decisions, and timebox each block.
- Freeze risky installs. In all CI jobs and ephemeral build agents, set
npm ci --ignore-scriptsornpm_config_ignore_scripts=truefor the next 48 hours. If that breaks legitimate scripts, scope exceptions to specific packages and jobs. Document every exception—and remove it in 72 hours. - Quarantine caches. Clear
npmcache on developer machines and CI (npm cache clean --force). Purge dependency caches in your CI provider. If you use Docker layers that includenode_modules, rebuild from clean base images. - Reinstall from known‑good locks. For each project, find the last green build before November 21, 2025. Reinstall with
npm cifrom that lockfile. If you must upgrade, pin only to versions published after maintainers have republished clean artifacts; do not let the solver float freely. - Search for IoCs. Grep your repos, build logs, and artifact stores for
setup_bun.jsandbun_environment.js. Also search your GitHub org for unexpected repos or branches referencing “Shai‑Hulud,” and for suspicious workflows (look for odddiscussion.yamlor newly registered self‑hosted runners). (scout.docker.com) - Rotate credentials on a schedule. Prioritize npm publishing tokens, GitHub PATs/fine‑grained tokens, and cloud keys exposed to build nodes. Rotate in this order: (a) organization‑level secrets, (b) repo‑level secrets, (c) developer machines. Track completion in a checklist with owners and timestamps.
- Harden GitHub Actions immediately. Set
GITHUB_TOKENtopermissions: contents: readby default; grant writable scopes only per‑job. Require environments with reviewers for deploy jobs; enable branch protections and required workflows. Prefer OIDC to cloud over static keys so you can nuke trust quickly. - Network egress guardrails. Restrict CI runners to talk only to your registry/proxy and approved endpoints. Temporarily block outbound to
webhook.siteand other known exfil domains until you complete forensics; log and alert on DNS lookups for suspicious hosts. - SBOM and attestations. Generate SBOMs (CycloneDX or SPDX) for production builds, then diff against a known‑good baseline from earlier in November. Store provenance/attestation with each artifact so you can roll back confidently.
- Validate prod exposure. If you vendor client‑side code, scan CDN bundles for unexpected preinstall artifacts or network calls. Server‑side, scan images for rogue files, extra cron entries, or modified
.npmrcentries. - Communicate. Post an internal FAQ: what happened, what we’re doing, how to report suspicious behavior. External comms if you ship SDKs: list affected versions, replacement versions, and a clear “how to clean” step list. Postman’s timeline is a good example of fast, transparent handling. (blog.postman.com)
Data points leaders will ask about
Expect questions about scope and timing. Analyses of the current wave indicate preinstall abuse via Bun‑linked payloads, with exfil to public repos branded as “Second Coming.” Reported impact ranges from thousands to tens of thousands of GitHub repos touched within the first day, and hundreds of npm packages seen or suspected. Use these ranges when briefing leadership, and anchor dates to November 21–24 for artifact triage. (esentire.com)
Where npm’s token changes collide with this incident
This fall’s token policy shifts on npm are meant to reduce exactly this blast radius. Classic tokens were disabled for creation on November 5, and GitHub signaled revocation and new flows on a tight November/December timeline. If your org still leans on long‑lived, write‑scoped tokens in CI, this is the week to finish migrating to granular tokens and—where possible—OIDC “trusted publishing.” (github.blog)
We’ve published no‑drama migration guides for these changes. Start with our CI‑first token migration playbook, and keep the deeper post‑deadline cleanup guide handy for rotating stragglers.
Can you safely disable npm lifecycle scripts?
Short answer: often, yes—temporarily. --ignore-scripts is a blunt instrument, but it’s effective while you audit. The trade‑offs: some toolchains (native modules, monorepo workspaces, or frameworks that compile assets on install) may rely on lifecycle hooks. Use targeted allowlists: run scripts only for known packages in a controlled job, keep everything else off. Document every exception so it doesn’t become permanent.
Hardening GitHub Actions and CI runners, step by step
Minimum policies to push today
Set a default permissions block to least privilege, then explicitly opt‑in jobs that need more. Require environments with reviewers for release jobs; turn on branch protection with required status checks; and add a “required workflow” that lints package.json to block lifecycle scripts sneaking into SDK repos. Limit self‑hosted runners to per‑repo, private network segments; no shared org‑wide runners for release pipelines.
OIDC to cloud, not static keys
Move deploy jobs to cloud‑provider OIDC so the runner gets a short‑lived credential tied to that job. If a runner is hijacked, killing the environment or job invalidates the trust chain; there’s no long‑lived key to rotate across dozens of repos.
Guardrails for developer machines
If you permit local publishing, enforce phishing‑resistant MFA (FIDO/U2F), short token lifetimes, and signed commits. Ensure ~/.npmrc doesn’t contain legacy tokens, and gate publish scripts behind a release bot workflow so humans don’t push directly to npm from laptops.
Dependency hygiene that actually scales
Put a private registry or proxy (e.g., company‑hosted npm proxy) in front of public npm. Promote packages into it only after scanning and manual approval during incidents like this. That gives you an emergency brake: you can block new versions at the proxy without touching app repos. Pair this with SBOMs and provenance—if you can’t tell which build pulled a tainted version, you’re guessing, not responding.
For a real‑world example of designing for failure and kill‑switches, see our resilience playbook for the recent Cloudflare outage—the principles translate well to dependency and CI isolation.
People also ask
How do I know if I installed a compromised package?
Check lockfiles and build logs for the affected window (Nov 21–24) and search artifacts for setup_bun.js/bun_environment.js. If in doubt, rebuild from a clean lock prior to the window and rotate secrets touched by those builds. (socket.dev)
Does this affect production servers?
Yes, if your deploys run npm install during image builds or on servers. Even if the malware didn’t persist on disk, secrets accessible during build could have been exfiltrated. Rebuild from clean bases and rotate credentials used by build agents.
Is Bun itself vulnerable here?
Bun is used as a loader in this campaign, but there’s no evidence that Bun is the root cause; the attack abuses install scripts to run arbitrary code, and Bun is simply the chosen mechanism to fetch and execute a large payload. (socket.dev)
What to do next (this week)
For developers:
- Turn on
--ignore-scriptsby default in CI for 72 hours while you audit; whitelist only what’s necessary. - Rotate npm/GitHub/cloud credentials touched by build nodes since Nov 21. Move deploys to OIDC if possible.
- Add a repository rule that rejects
preinstall/postinstallscripts in SDK and library repos unless explicitly approved. - Adopt an SBOM/provenance step in CI, and store the attestation next to the artifact in your registry.
For engineering leaders:
- Stand up a private npm proxy and freeze external fetches during incidents.
- Finish your npm token migration. Our Dec‑9 token change guide and the earlier migration playbook outline the safest paths to granular tokens and trusted publishing.
- Budget a half‑day “malware in install scripts” tabletop, including a dry‑run of cache purges and token rotations. If you need a template, our rapid‑fix playbook shows how to move from alert to clean build in hours, not days.
A closing reality check
You don’t need perfect security to survive a campaign like this. You need speed, containment, and the ability to revoke trust quickly. Disable install‑time scripts while you sweep, rebuild from clean locks, rotate anything that could have leaked, and lock CI to least privilege with OIDC to your cloud. Then put a proxy in front of the registry so the next wave is a non‑event for your delivery pipeline.
If your team needs a second set of eyes on hardening or incident cleanup, our engineering team can help assess CI trust boundaries, token strategies, and dependency isolation.
