Safeguard
Open Source Security

npm postinstall script malware trends

npm postinstall script malware surged 61% in H1 2026. Here's how attackers weaponize lifecycle hooks — and how to detect and stop them.

Safeguard Research Team
Research
7 min read

Between January and June 2026, Safeguard's research team catalogued more than 340 malicious npm packages pulled from the registry for abusing lifecycle scripts — a 61% increase over the same period in 2025. The common thread across nearly all of them: a single line buried in package.json, "postinstall": "node ./setup.js", quietly executing during npm install, long before any application code runs or any scanner has a chance to look at it.

This isn't a new technique. But it has become the dominant delivery mechanism for npm-based supply chain attacks, eclipsing typosquatting and dependency confusion as the preferred first-stage payload for both financially motivated crews and self-propagating worms. The trend picked up sharply after the Shai-Hulud campaign in late 2025 and has continued to accelerate through the first half of 2026, with copycat operators reusing and remixing the same install-time tooling against new sets of packages almost every month.

Why postinstall Scripts Are the Perfect Attack Surface

npm, like most package managers, supports lifecycle hooks — preinstall, install, postinstall, prepare — that let package authors run arbitrary shell commands automatically when a package is installed. The feature exists for legitimate reasons: compiling native bindings, downloading platform-specific binaries, generating build artifacts. The problem is that npm has historically executed these scripts by default, with no sandboxing, no network restrictions, and no prompt to the developer or CI runner installing the package.

From an attacker's perspective, that's close to ideal:

  • Guaranteed execution. No user interaction is required. The payload runs the moment npm install or npm ci completes dependency resolution — often inside a CI/CD pipeline with far more privileged credentials than a developer's laptop.
  • Full OS-level access. Lifecycle scripts run as native Node.js or shell processes, not inside the JavaScript sandbox a browser or bundler might impose. They can read environment variables, SSH keys, cloud credentials, .npmrc tokens, and CI secrets, and write to the filesystem freely.
  • Poor visibility. Most static analysis and SCA tooling scans source code and known-vulnerability databases; far fewer inspect package.json scripts fields or the behavior of the code they invoke at install time. A malicious postinstall step frequently ships in a package that has zero known CVEs.
  • Registry-native distribution. Because the payload lives inside a real, installable npm package, it inherits npm's implicit trust model. A compromised maintainer account or a stolen publish token turns an established, widely-depended-upon package into a distribution channel overnight.

A Timeline of Escalation

The technique isn't novel — event-stream (2018), ua-parser-js (2021), and the coa/rc incident (2021) all used install-time or runtime hooks to harvest credentials or drop cryptominers. What changed in the 2025–2026 window is scale and automation.

The Shai-Hulud campaign, first identified in September 2025, marked an inflection point: attackers packaged a self-propagating worm inside a postinstall script that harvested npm publish tokens from the local environment, then used those tokens to automatically republish the same malicious script into every other package the compromised maintainer had access to. Safeguard and other researchers tracked the worm spreading through hundreds of packages within days, some with millions of weekly downloads.

A second wave followed within weeks, using near-identical tooling with minor obfuscation changes to evade signature-based detection that had been rushed out in response to the first wave. By late 2025 and into early 2026, "mini" variants began appearing against narrower, more targeted sets of packages — including incidents affecting packages associated with the AntV and TanStack ecosystems — reusing the same core exfiltration logic (searching the filesystem for cloud credentials and CI tokens, then beaconing them to attacker-controlled endpoints or public paste services) but with smaller blast radii, apparently to stay under the radar of the registry's automated takedown response.

Across the incidents Safeguard has analyzed in this window, the postinstall payloads consistently follow a similar life cycle:

  1. Delivery — either through a newly published typosquat/lookalike package, a compromised maintainer account publishing a malicious version of a legitimate package, or a phished maintainer token used to push an unauthorized release.
  2. Execution — a postinstall (or preinstall/prepare) hook invokes a bundled or dynamically fetched script, frequently obfuscated with base64 or hex encoding, or staged as a second-stage download from a throwaway domain, Discord webhook, or even another npm package.
  3. Reconnaissance and harvesting — the script enumerates environment variables, .npmrc, .aws/credentials, .git-credentials, cloud CLI config, and CI-specific secret variables (GitHub Actions, GitLab CI, CircleCI tokens).
  4. Exfiltration — harvested data is sent out over HTTPS to attacker infrastructure, frequently disguised as telemetry or analytics traffic to blend in with legitimate outbound calls from a build environment.
  5. Propagation (in worm variants) — if publish credentials are found, the malware republishes itself into other packages the stolen token can access, repeating the cycle.

Why Traditional Scanning Misses It

Most software composition analysis tools were built to answer one question: does this dependency tree contain a package version with a known CVE? That model is necessary but not sufficient against postinstall malware, for a few structural reasons:

  • No CVE exists yet. A newly published malicious package or a hijacked maintainer release has no vulnerability database entry to match against. Detection has to rely on behavior or provenance signals, not known-bad version numbers.
  • The malicious code often isn't in the "application" dependency tree that gets reviewed. It can live several levels deep in a transitive dependency that no engineer consciously chose or read.
  • Lifecycle scripts run before static analysis of the installed code is typically performed, and in many CI configurations, npm install runs with --ignore-scripts disabled by default, meaning the payload has already executed and exfiltrated data before a security gate ever looks at the artifact.
  • Obfuscation defeats naive pattern matching. Base64-wrapped payloads, dynamically constructed strings, and multi-stage downloaders are trivial to generate and easily evade signature-based scanning that hasn't been updated for the specific sample.

What Security Teams Should Do Now

A few concrete controls meaningfully reduce exposure to this class of attack:

  • Default to --ignore-scripts in CI and treat lifecycle scripts as an explicit, reviewed exception, not a default-allow behavior. Most build steps that legitimately need a postinstall step (native module compilation, etc.) are a small, identifiable minority of the dependency tree.
  • Maintain a continuously updated SBOM covering the full transitive dependency graph, not just direct dependencies, so a compromised-package alert can be matched against your actual exposure in minutes rather than days.
  • Segment CI credentials and secrets so that a compromised install step in one project's pipeline cannot harvest tokens with broad blast radius across the org.
  • Monitor egress from build environments. Outbound connections from a CI runner during npm install to unfamiliar domains are a strong behavioral signal, even when the payload itself is well obfuscated.
  • Track maintainer and publish provenance, not just package names and versions — a sudden new maintainer, an unusual publish time, or a jump in lifecycle-script complexity between versions are all signals worth alerting on.

How Safeguard Helps

Safeguard is built to close exactly this gap between "known CVE" scanning and the behavioral reality of modern npm supply chain attacks. Our SBOM generation and ingestion pipeline maintains a continuously updated map of every direct and transitive dependency — including lifecycle scripts and their behavior — so a newly disclosed malicious package or hijacked maintainer account can be matched against your actual exposure the moment it's identified, not days later during a manual audit. Griffin AI, our security research agent, continuously monitors registry activity and flags anomalous postinstall behavior, obfuscated payloads, and suspicious publish patterns before they're picked up by public CVE feeds. Reachability analysis then tells your team whether a flagged package's malicious code path is actually invoked in your build or runtime, cutting through alert noise so engineers spend their time on the exposures that matter. And where remediation is straightforward — pinning to a clean version, removing an unused vulnerable transitive dependency, or adding a lockfile constraint — Safeguard opens an auto-fix pull request directly against your repository, turning a threat intelligence signal into a merged fix in minutes rather than a ticket that sits in a backlog.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.