Malicious Post-Install Scripts: npm and PyPI Attacks Explained

Installing a package isn’t a passive download. Both npm and PyPI can run code on your machine the moment a package lands even before your application imports it. That behavior is a deliberate, ubiquitous feature, and that’s exactly what makes it such an effective attack surface. Nearly a third of PyPI packages trigger code execution on install, and many malicious PyPI packages run their payload at install time rather than at import.

For an attacker, the install step is a valuable code-execution point that can fire automatically inside developer machines and CI/CD runners. A single poisoned install can expose whatever the runner can reach — cloud credentials, source code, signing keys — and in the worst case open a path to production and to customers, the kind of downstream exposure that lands on the CISO’s desk, not the developers.

What Is a Post-Install Script?

A post-install script is a lifecycle hook that a package manager runs automatically during installation, with the full permissions of the user or CI job that started the install and no confirmation prompt. It’s meant for legitimate setup work, but nothing stops it from doing anything else the user could do.

How npm Lifecycle Scripts Work

npm packages can define preinstall, install, and postinstall scripts in the scripts block of package.json. When you run npm install (or a CI runner executes npm ci), npm runs those scripts automatically, for the package and its dependencies, with the permissions and environment of whoever started the build, including any secrets exposed as environment variables.

How PyPI Install Hooks Work

On the Python side, installing from a source distribution (sdist) runs the package’s setup.py at build time, so pip install can execute code before the library is ever imported. An attacker can embed malicious commands directly in setup.py or override the setup command class so their code runs during installation. Prebuilt wheels skip setup.py, but nothing stops an attacker from shipping a sdist instead. As with npm, simply resolving the dependency is enough to execute the code, with no import or explicit run step required.

Why Do Post-Install Scripts Exist?

These hooks exist for good reasons: compiling native modules (for example, via node-gyp), downloading platform-specific binaries, generating configuration, or running other setup steps a package legitimately needs. That legitimacy is precisely the problem. Because real builds across the ecosystem depend on install scripts, you can’t simply switch them off without breaking things, which is what makes them such a durable hiding place for malicious code.

How Attackers Weaponize Post-Install Scripts

The attack chain is consistent across incidents and follows four steps.

Step 1: The Malicious Package Reaches the Build

The attacker gets a weaponized hook into your open-source dependencies through typosquatting, dependency confusion, a malicious transitive dependency, or a hijacked maintainer account that publishes a poisoned version of a trusted package. Sometimes the payload is hidden in a fake dependency that exists only to carry the install hook.

Step 2: The Install Script Executes Automatically

The moment npm install or pip install runs, the hook fires, with no user action, no prompt. In CI/CD this means immediate code execution inside the runner, with access to the environment variables, files, tokens, and mounted credentials exposed to that job.

Step 3: Secrets and Credentials Are Harvested

The script typically scours the host for value: .env files, SSH keys, cloud configuration, API tokens, and crypto wallets. Some payloads even download a secret-scanning tool like TruffleHog to find credentials more reliably, and read tokens straight out of CI environment variables.

Step 4: Stolen Data Is Exfiltrated Over the Network

Finally, the harvested data leaves. Payloads POST it to attacker-controlled infrastructure, push it into a newly created public GitHub repository, or pull down a second-stage remote access trojan. Whatever the method, the exfiltration is a network call made from inside the trusted build.

Real-World Examples of Post-Install Script Attacks

The s1ngularity Attack on Nx

On August 26, 2025, malicious versions of the popular Nx build system (~ 5.5 million weekly downloads) shipped a postinstall that scanned the filesystem for credentials, wallets, .env files, and SSH keys, then exfiltrated a multi-base64 encoded results.b64 file by creating a public “s1ngularity-repository.” In a first-of-its-kind twist, it weaponized installed AI CLI assistants to help with reconnaissance and harvesting. The malware ran mostly on developer machines, often via the Nx VS Code extension, and a follow-on wave used stolen tokens to flip 5,500+ private repositories across 400+ users and organizations to the public.

Deep dive: The s1ngularity Attack: Weaponizing AI CLI Tools and How InvisiRisk Stops It.

Shai-Hulud and Shai-Hulud 2.0

In September 2025, the Shai-Hulud worm became the first self-replicating malware seen in the npm ecosystem. Its postinstall payload ran TruffleHog to find secrets, exfiltrated them to attacker-created public GitHub repositories (a double-base64-encoded data.json), pushed malicious GitHub Actions workflows into victims’ projects, and used any stolen npm tokens to auto-publish itself into other packages the maintainer controlled. Initially, roughly 187 packages were confirmed compromised, later revised to 500+ packages, a scope that prompted a CISA alert on September 23, 2025. A second wave two months later – Shai-Hulud 2.0 (also called “The Second Coming”) – was more aggressive still: it executed at the preinstall stage, was identified across more than 25,000 GitHub repositories, and added destructive, wiper-style behavior that could shred a developer’s home directory if it couldn’t propagate or exfiltrate.

Full breakdown: Shai-Hulud npm Worm Attack: Overview and InvisiRisk Protection.

The Axios Maintainer Compromise

In late March 2026, an attacker hijacked an Axios (with ~100 million weekly downloads) maintainer account (no CVE, no change to the GitHub repo) and published malicious versions 1.14.1 and 0.30.4. They carried a malicious dependency, plain-crypto-js, that acted as a dropper for a cross-platform remote access trojan, executing at install time via a postinstall script and going after build secrets, cloud credentials, and environment tokens. Google Threat Intelligence attributed the campaign to a North Korea-nexus actor, termed Sapphire Sleet by Microsoft.

More: Axios npm Supply Chain Attack.

Why Traditional Defenses Don’t Stop Install-Time Attacks

Each standard control misses this class of attack for a structural reason, which is why build-time security for CI/CD pipelines has become its own layer.

SCA Tools Scan Before the Script Runs

Software composition analysis reasons from manifests and known-vulnerability or known-malware databases. A freshly published malicious version, whether a zero-day, or a poisoned release from a compromised maintainer, isn’t in those databases yet, and traditional SCA does not observe or control what the install script actually does when it executes. The same gap applies to a package firewall: it can block dependencies with known vulnerabilities at download time, but a brand-new poisoned release carries no known signature, so it passes straight through.

Disabling Install Scripts Breaks Legitimate Builds

npm install –ignore-scripts blocks the attack, but it also breaks every package that legitimately compiles native modules or fetches binaries. Teams can’t keep it on across real-world builds, so it isn’t a durable control.

Post-Build Scanning Sees the Damage, Not the Action

Secret scanners and artifact scans run after the fact. By the time they look, the install script has already read the secret and made its network call. You detect the aftermath, not the exfiltration that mattered.

Stop Post-Install Script Attacks at Build Time with InvisiRisk

The common thread across Shai-Hulud, Axios, and s1ngularity is that the malicious action happens at install and build runtime, the one moment none of the standard tools observe. That’s the layer InvisiRisk’s Build Application Firewall (BAF) defends:

  • Network-boundary interception. BAF intercepts every outbound connection from the build, so the exfiltration POST, the push to a new public repo, or the RAT’s C2 beacon is blocked before any data leaves the runner.
  • Secret-in-transit detection. Outbound build requests are inspected for encoded tokens and keys, catching exfiltration even over TLS.
  • Unauthorized-action policy. Builds do write to known places, like pushing tags and releases to their own repo. Anything outside that scope, such as creating a new public repository, exactly what Shai-Hulud and s1ngularity relied on, can be blocked.
  • Stability buffer. Freshly published versions (for example, under 48 hours old) are blocked before they can enter a build, directly covering maintainer-compromise cases like Axios where the malicious release is brand new.
  • AI anomaly detection. After the build, AI-based analysis flags anomalous build-time activity, including novel install-time behavior that signature-based tools miss, even when it comes from an otherwise trusted package.


Install scripts aren’t going away. They’re load-bearing for the whole ecosystem. The realistic defense isn’t to ban them but to treat npm install and pip install as untrusted code execution – making CI/CD pipeline security a matter of enforcing policy at the build boundary, while it runs.

See how InvisiRisk secures the build pipeline: read What Is Build-Time Security for CI/CD Pipelines? or request a demo.