TL;DR
On March 21, 2026, Microsoft Threat Intelligence published a technique profile on malicious npm lifecycle scripts — the mechanism behind multiple high-profile supply chain attacks including Shai-Hulud 2.0, Contagious Interview (North Korea), and campaigns by Jade Sleet, Moonstone Sleet, and Sapphire Sleet.
The core problem: when you run npm install, any package in your dependency tree can execute arbitrary code with your user’s full privileges — filesystem, environment variables, network, SSH keys, cloud credentials. No sandbox. No approval prompt. No signature verification.
This isn’t a vulnerability to patch. It’s intended functionality being weaponized.
Why Lifecycle Scripts Are Dangerous
npm lifecycle scripts are hooks defined in package.json that fire automatically during package operations. The execution order during install:
preinstall → install → postinstall → prepublish → prepare
Every package in the dependency tree — including transitive dependencies you never explicitly installed — gets to run these hooks. The installing user’s full privileges are inherited.
Here’s what makes this particularly nasty:
- No sandboxing: Scripts get full access to the filesystem, environment variables, network, and child processes
- Transitive execution: A malicious package buried five levels deep in your dependency tree still fires its
postinstall - Silent execution: Unless you’re watching terminal output carefully, you won’t notice the extra script execution
- Legitimate by design: Malicious scripts use the same binaries and exhibit the same behaviors as legitimate build scripts, making detection a false-positive minefield
What’s Accessible
| Resource | Examples |
|---|---|
| Credential files | .npmrc, .git-credentials, ~/.ssh/*, ~/.azure/, ~/.aws/credentials |
| Environment tokens | NPM_TOKEN, GITHUB_TOKEN, AZURE_CLIENT_SECRET, AWS_ACCESS_KEY_ID |
| Network | HTTP/HTTPS, DNS, WebSockets |
| Child processes | PowerShell, bash, curl, interpreters |
A single postinstall script can read your .npmrc, steal your npm publish token, exfiltrate it over HTTPS, and you’d never know unless you were specifically monitoring for it.
The Attack Patterns
Malicious lifecycle scripts enable several capabilities that execute immediately on install:
Direct execution — Malicious postinstall runs inline JavaScript or spawns a child process. Simple, fast, effective.
Staged payloads — The install script fetches, decodes, and runs a remote payload. This separates the trigger from the actual malware, evading static analysis. The package.json looks clean; the real payload lives on an attacker-controlled server.
Credential theft — Scripts read .npmrc, .git-credentials, ~/.ssh/*, or enumerate process.env. Credentials get exfiltrated over HTTP to attacker infrastructure. This is the bread and butter of npm supply chain attacks.
Worm-style propagation — Stolen npm tokens are used to republish trojanized versions of the victim’s own packages. This is exactly what Shai-Hulud 2.0 did — automated pipelines accelerated the spread from one compromised maintainer to their entire package ecosystem.
How Attackers Get In
The initial compromise typically happens through:
- Maintainer account takeover — password compromise, phishing, or expired email domain registration
- CI/CD workflow compromise — injecting malicious steps into build pipelines
- Typosquatting —
lodasshinstead oflodash - Dependency confusion — publishing a public package matching an internal package name
North Korea’s Sapphire Sleet has been observed registering typosquatted npm domains specifically to phish maintainer credentials.
Real-World Campaigns
These aren’t theoretical attacks. Multiple threat actors have operationalized this technique:
Shai-Hulud / S1ngularity (Aug–Nov 2025)
The Shai-Hulud campaigns used malicious postinstall scripts to steal npm tokens and credentials. In later waves, stolen npm tokens were used to inject the same payload into other packages the victims maintained — creating a self-replicating worm effect across the npm ecosystem. JFrog tracked two distinct waves of this campaign.
Contagious Interview (Oct 2025)
Tracked by Socket, this campaign deployed postinstall scripts in typosquatted packages that executed in-memory loaders. The loaders stole credentials and cryptocurrency wallets. Over 338 malicious packages were published with more than 50,000 downloads.
Moonstone Sleet (Jan 2024+)
This North Korean actor targeted individuals looking for work with a coding skills test. The test project contained malicious npm packages — when victims ran npm install to set up the project, the lifecycle hooks delivered malware.
Jade Sleet (Jul 2023)
GitHub reported with high confidence that Jade Sleet used social engineering to convince cryptocurrency developers to clone attacker-controlled repositories. When victims ran npm install, malicious preinstall hooks downloaded and executed additional malware.
MITRE ATT&CK Mapping
| Tactic | Technique | How It Applies |
|---|---|---|
| Initial Access | T1195.002 — Supply Chain Compromise | Compromised registries, maintainer accounts, or CI/CD inject malicious lifecycle scripts |
| Initial Access | T1199 — Trusted Relationship | Exploiting implicit trust in third-party packages |
| Execution | T1204 — User Execution | Developers lured into running npm install on attacker-controlled repos |
| Execution | T1059.007 — JavaScript | Lifecycle scripts run JS through Node.js during install |
| Execution | T1059.001 — PowerShell | On Windows, scripts often spawn PowerShell for staged payloads |
| Defense Evasion | T1027 — Obfuscation | Base64 encoding, string concatenation, encrypted payloads |
| Credential Access | T1552.001 — Credentials in Files | Reading .npmrc, .git-credentials, enumerating process.env |
| Credential Access | T1552.004 — Private Keys | Reading ~/.ssh/* for lateral movement |
| C2 | T1105 — Ingress Tool Transfer | Staged payloads fetched from attacker servers during install |
| Exfiltration | T1041 — Exfil Over C2 | Credentials POSTed to attacker infrastructure |
| Exfiltration | T1567 — Exfil Over Web Service | Credentials sent to Discord webhooks, Slack, Telegram, Pastebin |
Detection
Detection is hard because lifecycle scripts are intended functionality, not a bug. Malicious scripts look identical to legitimate build scripts. That said, there are signals worth monitoring.
Microsoft Defender Signatures
Microsoft Defender Antivirus detects known malicious npm payloads:
Trojan:JS/S1ngularityTrojan:JS/ShaiWorm
Behavioral detections (may trigger on legitimate activity too):
Behavior:Win32/NodeJS.ABehavior:Win32/NodeNetConnect.A
Defender for Endpoint Alerts
High-confidence alerts:
- Suspicious Node.js process behavior
- Suspicious NPM package retrieval
Lower-confidence (may be unrelated activity):
- Jade Sleet threat actor activity detected
- Moonstone Sleet actor activity
- Possible supply chain attack activity
- Script execution via workflow runner
- Suspicious script launched
What to Hunt For
In your CI/CD logs and endpoint telemetry, look for:
nodeornpmprocesses spawning unexpected child processes (curl,wget,PowerShell)- Outbound HTTP POST requests during
npm installto non-registry domains - File reads of
.npmrc,.git-credentials, or~/.ssh/during package installation - Environment variable enumeration (
process.envdumps) in install contexts - Packages with
postinstallscripts that fetch remote payloads
Defensive Strategy
The only client-side control that completely prevents lifecycle script execution is --ignore-scripts. Everything else limits impact after execution has already occurred.
CI/CD Pipeline Hardening
This is your highest-leverage control.
| |
For packages that legitimately need lifecycle scripts (native modules like node-sass, sharp), maintain an explicit allowlist.
pnpm 10+ blocks lifecycle scripts by default unless explicitly allowed. Bun also does not execute lifecycle scripts for dependencies by default. If you’re evaluating package managers, this is a meaningful security differentiator.
Dependency Hygiene
- Run
npm auditregularly and fix withnpm audit fix - Prevent dependency confusion: use scoped package names (
@yourorg/package-name) routed to your private feed - Pin dependencies to exact versions in production
- Review
postinstallscripts before adding new dependencies
Development Environment Isolation
- Use containerized dev environments (GitHub Codespaces, VS Code Dev Containers) for high-risk teams
- Distribute secure
.npmrcdefaults through dev container config - Run
npm installin container jobs with read-only volume mounts
Identity & Access Controls
- Deploy Conditional Access with Identity Protection for developer identities
- Use step-up authentication on
npm publishoperations - Configure just-in-time access (PIM) for package publishing permissions
- Enable npm Trusted Publishing with provenance attestations
For Organizations Publishing npm Packages
- Enable npm provenance attestations — this lets consumers verify where a package was built
- Use GitHub environments with protection rules for publishing workflows
- Require signed commits through organizational rulesets
- Protect
.github/workflows/**,package.json, andpackage-lock.jsonwith push rulesets and CODEOWNERS - Enable secret scanning with push protection organization-wide
If You Get Hit
Assume credential exfiltration has occurred. Immediate response:
- Rotate everything — npm tokens, GitHub tokens, service connection credentials, SSH keys, cloud credentials
- Audit publishes — check if your npm packages were republished with malicious payloads
- Check downstream — notify consumers if your packages were compromised
- Review audit logs — GitHub audit logs, Azure DevOps audit logs, npm access logs
- Deploy monitoring — stream audit logs to your SIEM for analytic rules and hunting queries
The Uncomfortable Truth
npm lifecycle scripts are not a vulnerability. They’re a feature. You can’t file a CVE against postinstall. There’s no patch coming.
The npm ecosystem has fundamentally chosen convenience over security: any package, at any depth in your dependency tree, can run arbitrary code with your credentials during installation. The only complete mitigation is --ignore-scripts, which breaks legitimate packages that need native compilation.
pnpm 10 and Bun have made the right call by defaulting to blocking lifecycle scripts. npm and yarn still execute them by default. Until that changes, every npm install is an implicit trust decision across your entire dependency graph.
The supply chain attacks aren’t stopping. Shai-Hulud demonstrated self-replicating worm behavior through npm. North Korean threat actors (Jade Sleet, Moonstone Sleet, Sapphire Sleet) are actively targeting developers through typosquatting, fake job interviews, and social engineering.
Lock down your CI/CD pipelines with --ignore-scripts. Isolate your dev environments. Monitor for credential exfiltration during installs. And audit your dependency tree — because somewhere in those 1,847 transitive dependencies, there might be a postinstall script waiting to run.
References
- Microsoft Threat Intelligence — Technique Profile: Malicious npm lifecycle scripts (March 21, 2026)
- Socket — North Korea’s Contagious Interview Campaign Escalates: 338 Malicious npm Packages
- JFrog — Shai-Hulud: The Second Coming
- GitHub Security Blog — Security alert: social engineering campaign targets technology industry employees
- npm Documentation — Scripts
- OWASP — NPM Security Cheat Sheet
