Skip to main content
Disabling preinstall Scripts Won't Save YouGeneral
4 min readFor Security Engineers

Disabling preinstall Scripts Won't Save You

You've probably heard that disabling lifecycle scripts in your package manager is a good idea. After all, the npm worm that targeted [email protected] and spread to 353 compromised versions across 79 packages used a preinstall script to steal credentials. So, just turn off scripts and you're safe, right?

Not exactly.

Why Script Blocking Isn't Enough

Disabling scripts is like treating a symptom, not the disease. The real issue isn't that npm runs preinstall scripts. It's that your dependency chain is a black box, and you're trusting hundreds of transitive dependencies you've never audited.

Blocking scripts can break legitimate packages. Many popular dependencies use postinstall for tasks like compiling native modules or downloading platform-specific binaries. When you block all scripts, you either break your build or start making exceptions, which weakens your policy.

The keyv worm didn't succeed just because preinstall scripts exist. It succeeded because:

  • Development environments had credentials available to steal.
  • CI systems ran with excessive permissions.
  • Teams lacked visibility into their dependencies' actions.
  • There was no verification step between "package published" and "package installed."

What Actually Happened

SafeDep verified that the worm executed a credential-stealing bundle inside developer and CI environments. The payload didn't just grab npm tokens. It also planted hooks in Claude Code and VS Code configuration files, establishing persistence that survived script blocking.

On August 4 at 5:40 p.m. IST, npm package pages showed earlier releases restored as latest for at least nine packages. This game of whack-a-mole highlights the problem: reactive script blocking can't stop an attack already inside your environment.

If you'd disabled preinstall scripts, you might have blocked the initial infection vector. But you wouldn't have:

  • Detected the 79 compromised packages in your dependency tree.
  • Prevented credential exposure from earlier installations.
  • Stopped the VS Code hooks from executing when developers opened projects.
  • Identified which CI builds were compromised.

What to Do Instead

1. Implement Dependency Verification Before Installation

Use tools that check package signatures and compare hashes against known-good registries. This catches tampering before any code runs. For PCI DSS v4.0.1 environments, Requirement 6.4.3 already mandates reviewing custom code before deployment. Extend that principle to dependencies.

2. Enforce Least-Privilege Credentials Everywhere

The worm stole credentials because they were available. Your CI system doesn't need an admin token that can publish packages. Scope tokens to exactly what each job requires. If a build only needs to read from your private registry, give it read-only access.

Create separate tokens for:

  • Package installation (read-only)
  • Publishing (scoped to specific packages)
  • CI builds (temporary, job-specific)

3. Monitor Dependency Changes, Not Just Script Execution

Set up alerts when:

  • A package you depend on publishes a new version.
  • A transitive dependency changes.
  • Package maintainers change.
  • A package's download count spikes unexpectedly.

The 353 poisoned versions didn't appear overnight. There was a propagation pattern that automated monitoring could have flagged.

4. Isolate Build Environments from Production Credentials

Your CI system should never have access to production AWS keys, database passwords, or API tokens. Use separate credential stores. If you're running SOC 2 Type II audits, this separation is already part of your logical access controls. Apply the same principle to development environments.

5. Use Lock Files Religiously and Audit Them

Lock files pin exact versions, including transitive dependencies. When [email protected] was published, teams with properly maintained lock files didn't automatically pull it. Review lock file changes in pull requests the same way you review code changes.

For NIST Cybersecurity Framework v2.0 alignment, this falls under your supply chain risk management function (SC.SupC). You need visibility into what's changing and why.

When Script Blocking Is Right

Lifecycle scripts aren't harmless. There are legitimate cases for blocking them:

In CI Environments You Don't Fully Control: If you're using shared runners or managed CI services, blocking scripts adds defense in depth. You don't know what else is running on that infrastructure.

For Packages You've Never Vetted: When evaluating a new dependency, install it with scripts disabled first. Review what it's trying to do. Then decide if those scripts are necessary.

As an Emergency Response Measure: If you detect active compromise, blocking scripts buys you time to assess the damage. It's a tourniquet, not a cure.

For High-Security Environments: If you're building systems that handle payment card data or meet ISO 27001 requirements, script blocking might be part of your baseline hardening. Just don't let it be your only control.

The keyv worm exposed real gaps in how we manage dependencies. But the answer isn't to disable the features that make package managers useful. It's to build visibility, enforce least privilege, and verify before you trust. Block scripts if it fits your threat model, but don't mistake it for comprehensive supply chain security.

Your dependency tree is part of your attack surface. Treat it that way.

Topics:General

You Might Also Like