Safeguard
Supply Chain Security•

Is It Safe to Run npm install (or pip install) Right Now?

Mostly yes, if you install from a lockfile, skip install scripts and avoid versions under a few days old. Here is how to set that up and check fast.

Safeguard Security Team
37 min read

Usually yes, but not the way most people run it. Installing exact versions from a committed lockfile, with dependency install scripts turned off and a cooldown that skips versions published in the last few days, removes most of the risk from the worm and hijacked release waves of 2025 and 2026. A plain npm install or pip install that pulls the newest version of everything, with your credentials in the environment, is the risky part.

The question keeps coming back because the incidents keep coming back. On 21 October 2026 a compromised LiteLLM release reached PyPI and Docker Hub with a credential stealer inside. In August, Singapore's Cyber Security Agency warned about a Shai-Hulud family worm that had touched more than 1,300 npm package versions. In each case, the dangerous window was hours, not weeks, and the people who got hurt were the ones who installed the newest version during those hours.

This post explains what actually runs when you install a package, how long bad versions tend to stay live, which defenses work and where each one stops, how to check in minutes whether a bad version reached a laptop, a CI cache or a container image, and what to rotate if it did. It covers npm, pnpm and Yarn on the JavaScript side and pip and uv on the Python side.

Key takeaways

  • A cooldown of a few days removes most of that exposure. npm, pnpm, Yarn, pip and uv all now have a minimum release age setting. Security fixes need a documented way around it.
  • A lockfile protects you only when you install from it. npm ci and frozen installs do; npm install some-package, npx, global installs and unpinned Dockerfile lines do not.
  • Install scripts are the main way npm malware runs. pnpm 10 and later, and current Yarn, already block dependency scripts by default. On npm you set it yourself.
  • Python has no single off switch. Source builds run code, and a .pth file runs code every time any Python process starts, even if you never import the package. Prefer wheels, hashes and a cooldown.
  • If a bad version ran, assume the host is compromised. Rotate from a clean device, starting with the tokens that let the attack spread.

What actually runs on my machine during npm install or pip install?

An install is closer to running a stranger's program with your account, environment and network than to a download.

npm, pnpm and Yarn: lifecycle scripts

An npm package can declare scripts in its package.json that run during installation. According to the npm scripts documentation, installing a package runs its preinstall, install and postinstall scripts, plus the prepare family in some cases. If a package ships a binding.gyp file and declares no install script of its own, npm defaults to running node-gyp rebuild, which compiles native code on your machine.

That is the mechanism almost every npm worm uses. The Shai-Hulud worm of September 2025, as GitHub described it, spread "by injecting malicious post-install scripts into popular packages" (GitHub blog). The script runs the moment the package lands, before any of your code imports it, and it inherits whatever is in your environment: npm tokens, GitHub tokens, cloud keys, SSH agent access.

Three other paths matter:

  • The package's own code at import time. Even with scripts off, a malicious package runs when your application or test suite imports it. Turning off scripts moves the risk from install time to run time; it does not remove it.
  • Git and tarball dependencies. A dependency pointing at a git URL can be built from source, which may run its prepare script. npm's allow-git setting, which defaults to all, limits fetching from git references (npm config).
  • npx and one-off CLIs. npx some-tool downloads and runs the newest matching version unless you name one. There is no lockfile in the loop.

pip and uv: builds, wheels and .pth files

Python installs have more ways to run code, and none of them has a universal off switch.

  • Source distributions (sdists) run a build. If no compatible wheel exists, pip or uv builds one by running the package's build backend, which is arbitrary Python code (setup.py or whatever the pyproject.toml build system specifies). The pip secure installs guide says plainly that by default pip "involves running arbitrary code from distributions".
  • Wheels do not run code at install time, but they can drop files that run later. A wheel is unpacked, not executed. The catch is what it unpacks.
  • .pth files run at every Python start. Files ending in .pth in site-packages are processed whenever the interpreter starts. The Python site documentation says lines that begin with import are executed and "are always run at Python startup", regardless of whether the module is used. The current docs mark these import lines as deprecated from Python 3.15, but they still execute during the deprecation period.
  • sitecustomize.py also loads at startup. A package that drops one at the root of site-packages gets the same effect.

The October 2026 LiteLLM release used both. According to an independent technical analysis, installing version 1.107.0 added cook.pth, cook.py and sitecustomize.py to site-packages, so the stealer ran in "any Python process" on the host: your app, pip itself, your editor's language server and CI jobs. The OSV entry MAL-2026-2144 notes that the same .pth mechanism appeared in the compromised 1.82.7 and 1.82.8 releases in March.

What the attacker gets

Whatever the install process can read. Per the independent analysis, the October LiteLLM payload went after SSH keys, cloud and Kubernetes credentials, GitHub CLI tokens, CI secrets, AI coding tool tokens, browser passwords and environment variables, then installed an hourly backdoor. Singapore's advisory on the August npm worm lists a similar set (CSA AD-2026-009). The target is the developer's keys, because those keys publish the next round of packages.

How long do malicious versions usually stay live?

Short enough that "right now" is the right way to think about it, and long enough that thousands of machines can be hit.

IncidentEcosystemTime live before removalSource
LiteLLM, March 2026PyPI2 h 32 min from upload to quarantine; over 119,000 downloads in the windowPyPI incident report
Telnyx, March 2026PyPI3 h 42 min from upload to quarantinePyPI incident report
LiteLLM 1.107.0, 21 October 2026PyPIAbout 1 h 44 min (11:39 to 13:23 UTC)Independent analysis
LiteLLM 1.107.0, 21 October 2026Docker HubAbout 2 h 34 min until tags were removed or repointedIndependent analysis
Shai-Hulud, September 2025npmGitHub was notified 14 September and removed over 500 packagesGitHub, CISA

Two numbers in PyPI's report explain why the window matters so much. First, users sent 13 reports about the March LiteLLM release, and those reports drove the quarantine. Second, PyPI estimates that 40 to 50 percent of installs fetched the latest version unpinned. That second group is the population a cooldown protects.

PyPI's quarantine feature hides a reported project from the simple index so it is no longer installable while admins review it. That shortens the window but does not close it, because someone has to notice and report first. For a closer look at npm's side of the same problem, see our post on npm takedown times during Shai-Hulud.

What this means for "right now": on any given day the chance that the version you pull is malicious is low, but it rises sharply for versions published in the last few hours, and almost all documented damage happened inside that window. A rule of "nothing published in the last three days" would have kept every incident in the table above off your machine.

Do a lockfile, npm ci and a frozen lockfile protect me?

Yes, against new malicious versions of packages you already depend on, as long as the lockfile was written before the bad version existed and you install from it without letting it change. They do not protect you in the many cases where the lockfile is bypassed or regenerated.

What they do

A lockfile records the exact version and integrity hash of every package in the tree. The OpenSSF npm best practices guide says "Hash pinning informs the package manager of the expected hash for each dependency without trusting the registries." The npm ci documentation adds two useful properties: it requires an existing lockfile, and if package.json and the lockfile disagree it "will exit with an error, instead of updating the package lock". It also deletes any existing node_modules first, so a stale tree cannot hide anything.

The equivalents elsewhere:

# npm: install exactly what the lockfile says, fail on drift
npm ci

# pnpm
pnpm install --frozen-lockfile

# Yarn (modern)
yarn install --immutable

# uv: fail if uv.lock is out of date, never re-resolve
uv sync --locked

# pip: only exact, hashed requirements
pip install --require-hashes -r requirements.txt

When they do not

SituationLockfile protects you?Why
npm ci in CI with a lockfile committed last weekYesExact versions and hashes from before the bad release
npm install with no arguments on a laptopUsuallynpm respects the lockfile but can update it if package.json ranges no longer match
npm install new-package or npm updateNoResolves fresh versions and rewrites the lockfile
An automated dependency update pull request merged the same dayNoThe new lockfile can contain the bad version
npx tool, npm install -g tool, pipx install toolNoNo project lockfile involved
RUN pip install litellm in a DockerfileNoResolves the newest version at build time
pip install -r requirements.txt with ranges and no hashesNoRanges resolve to the newest match
pip freeze > requirements.txt used as a lockPartlyPins versions but, as PyPI points out, omits checksums
A lockfile generated during the bad windowNoIt faithfully records the malicious version

The last row catches teams out. A lockfile records a past resolution; it does not judge it. If someone ran npm install on a bad day and committed the result, every later npm ci installs the bad version perfectly. So review lockfile changes in pull requests, and pair the lockfile with a cooldown: the cooldown stops a fresh resolution picking something brand new, and the lockfile stops anything changing afterwards.

Should I set a minimum release age or cooldown, and will it delay security fixes?

Yes, set one. Three days is a reasonable default for most teams; a week is defensible for production services. It will delay fixes unless you give security updates an explicit bypass, so build that bypass in from day one.

The settings, per tool

ToolSettingUnitDefaultPer-package exception
npmmin-release-age in .npmrcDaysOff (null)Override on the command line for one install
pnpmminimumReleaseAge in pnpm-workspace.yamlMinutes1440 (one day) in current docsminimumReleaseAgeExclude
YarnnpmMinimalAgeGate in .yarnrc.ymlDuration such as 1w1w in current docsnpmPreapprovedPackages
uvexclude-newer in uv.toml or pyproject.tomlTimestamp or duration such as 3 daysOffexclude-newer-package, set a package to false
pipuploaded-prior-to optionISO 8601 date or duration such as P3DOffRun a one-off install with P0D

Sources: npm config, pnpm supply chain security, Yarn configuration, uv settings, pip install options.

Configuration that implements a three day cooldown:

# .npmrc (needs a recent npm; check npm config docs for min-release-age)
min-release-age=3
# pnpm-workspace.yaml
minimumReleaseAge: 4320        # minutes, 3 days
minimumReleaseAgeExclude:
  - "@your-org/*"              # your own packages, published by your own pipeline
# .yarnrc.yml
npmMinimalAgeGate: "3d"
npmPreapprovedPackages:
  - "@your-org/*"
# pyproject.toml
[tool.uv]
exclude-newer = "3 days"
# pip: per command
pip install --uploaded-prior-to P3D -r requirements.txt

Two caveats for pip: its documentation says the option is "Only effective when using indexes that provide upload-time metadata", so check your internal mirror, and PyPI's report says the flag arrived in pip 26.0 with the config setting in 26.1.

Will it delay security fixes?

Yes, if you let it apply to everything. When a critical CVE lands and the fixed release is six hours old, a three day cooldown blocks the fix. The LiteLLM discussion on Hacker News raised exactly this: one commenter reported that cooldowns delay security patches and need manual exceptions, and others pointed out that a fixed, public cooldown lets a patient attacker time a release to mature before anyone installs it (HN thread).

The practical answer has three parts:

  1. Write down the bypass. For npm, install the fixed version with an override for that one command. For pnpm, Yarn and uv, add the package to the exception list in the same pull request as the fix, with a comment and an expiry date. For pip, run the one install with P0D, which PyPI's own report suggests.
  2. Gate the bypass on evidence. Only bypass for a version that an advisory names as the fix, and confirm the version against the advisory or a lookup such as Gold Open Source before you merge.
  3. Keep the cooldown short enough to be honest. A 30 day cooldown will be bypassed so often that it stops meaning anything. Three to seven days catches the documented incidents without becoming routine paperwork.

PyPI's report also notes that common automated update tools already let security updates skip their own cooldowns by default. If you use one, check that setting rather than assuming it. We go deeper on the trade-offs in minimum release age and cooldown policies.

How do I turn off install scripts by default without breaking native modules?

Turn scripts off globally, then allow the handful of packages that genuinely need them. Most projects need fewer than five.

npm

npm runs scripts by default. The ignore-scripts setting defaults to false; when true, "npm does not run scripts specified in package.json files".

# 1. Turn scripts off for this project (or use ~/.npmrc for your user)
echo "ignore-scripts=true" >> .npmrc

# 2. Install as usual
npm ci

# 3. Find which installed packages declare install-time scripts
npm query ':attr(scripts, [postinstall])' | grep '"name"'
npm query ':attr(scripts, [install])' | grep '"name"'

# 4. Rebuild only the ones you trust and need
npm rebuild esbuild sharp --ignore-scripts=false

Common legitimate needs are image libraries, bundlers with native cores and database drivers, and many of these now ship prebuilt binaries as optional platform packages that need no script. Test with scripts off before adding anything back, and keep the allowed list in the repository so a reviewer sees when it grows.

pnpm

pnpm made this the default. Its supply chain security page says "pnpm v10 disables the automatic execution of postinstall scripts in dependencies." You approve exceptions with pnpm approve-builds, which writes an allowBuilds map:

pnpm approve-builds esbuild sharp !core-js
# pnpm-workspace.yaml
allowBuilds:
  esbuild: true
  sharp: true
  core-js: false

pnpm warns against the global dangerouslyAllowAllBuilds switch. Two other pnpm settings are worth turning on at the same time: trustPolicy: no-downgrade, which refuses a package whose trust level has dropped since earlier versions, and blockExoticSubdeps: true, which stops transitive dependencies from pulling from git URLs or tarballs.

Yarn

Current Yarn documentation lists enableScripts with a default of false, meaning third-party postinstall scripts do not run, and dependenciesMeta in package.json lets you re-enable one package. Older Yarn versions behaved differently, so check yarn --version and set the value explicitly rather than relying on the default.

What you give up

Scripts off breaks packages that compile on install with no prebuilt binary for your platform. The fix for each is one line in the allowed list. In return, a hijacked release of any of your other dependencies cannot run code at install time, though it can still run when imported, which is why scripts off pairs with a cooldown.

How do I check, in minutes, whether a compromised version reached my laptop, CI cache or container images?

Work from the advisory. Every credible advisory names a package and one or more versions. Your job is to answer "does that exact version exist anywhere we run code", in this order: laptops and dev containers, CI runners and caches, built images, and anything already deployed.

Step 1: Confirm the bad versions from a primary source

Take the version list from the maintainer's notice, the registry, a national CERT or an OSV entry, not from a social media post. You can query OSV directly:

curl -s -d '{"package":{"name":"litellm","ecosystem":"PyPI"},"version":"1.107.0"}' \
  https://api.osv.dev/v1/query | python3 -m json.tool | head -40

A MAL- identifier in the response means the version is recorded as malicious.

Step 2: Search repositories and lockfiles

# npm: is the package anywhere in the tree, at which versions?
npm ls keyv --all

# Search every lockfile in a directory of checkouts for one exact version
grep -rn --include=package-lock.json -A2 '"node_modules/keyv"' ~/code | grep '"version": "6.0.0"'
grep -rn --include=pnpm-lock.yaml 'keyv@6.0.0' ~/code
grep -rn --include=yarn.lock -A1 '^"\?keyv@' ~/code | grep '6.0.0'

# Python lockfiles and requirements
grep -rn --include=uv.lock -A1 'name = "litellm"' ~/code | grep '1.107.0'
grep -rn --include='requirements*.txt' -i 'litellm==1.107.0' ~/code

npm ls shows transitive copies too, which matters because most people who received a compromised package never asked for it by name. The CSA advisory on the August npm wave lists keyv, cacheable, flat-cache and file-entry-cache, which are deep dependencies of common build tools.

Step 3: Check what is actually installed on the machine

# Global npm packages
npm ls -g --depth=0

# Python: every interpreter and virtual environment you use
python3 -m pip show litellm 2>/dev/null | head -3
uv pip show litellm 2>/dev/null | head -3

# Look for .pth files that execute code, in the active environment
SITE=$(python3 -c 'import site; print(site.getsitepackages()[0])')
grep -l '^import' "$SITE"/*.pth
ls -la "$SITE"/sitecustomize.py 2>/dev/null

Most .pth files contain only paths. A handful of well-known tools legitimately use import lines, so review the list rather than deleting everything that matches. For a specific incident, use the indicator files the advisory names; for LiteLLM 1.107.0, the independent analysis lists cook.pth, cook.py and sitecustomize.py, along with marker files in /tmp and a detached Python process.

Step 4: Clear local caches

Package caches are an easy way for a removed version to come back on the next offline or cached install.

npm cache clean --force
pnpm store prune
yarn cache clean
pip cache purge
uv cache clean litellm

If you want evidence before clearing the npm cache, its index records the tarball URLs it fetched:

grep -rl 'keyv/-/keyv-6.0.0.tgz' ~/.npm/_cacache/index-v5 | head

Step 5: Check CI caches and runners

CISA's Shai-Hulud alert specifically says to "Search for cached versions of affected dependencies in artifact repositories and dependency management tools" (CISA). That includes CI dependency caches, internal registry mirrors and self-hosted runners that keep state between jobs.

# GitHub Actions caches for a repository
gh cache list --limit 100
gh cache delete --all      # after you have recorded what was there

Then check build logs for the window. A job that ran npm install or pip install without a lockfile between the publish time and the removal time is a candidate, and on a self-hosted runner the runner itself may be compromised.

Step 6: Check container images

Images built during the window can carry the bad version long after the registry has removed it.

# Python image
docker run --rm --entrypoint sh your-image:tag -c 'pip show litellm 2>/dev/null | head -2'

# Node image
docker run --rm --entrypoint sh your-image:tag -c 'cd /app && npm ls keyv --all'

Running a suspect image to inspect it is itself a risk for Python images, because the .pth payload runs when the interpreter starts. Prefer inspecting the image without starting Python, for example by scanning the image or its SBOM. With the Safeguard CLI:

safeguard scan --image your-image:tag
safeguard scan --sbom ./sbom.cdx.json
safeguard scan --dir .

Results land as JSON under ./output, and the Safeguard platform shows which projects contain a given component and version, so one search answers the question across every repository, image and SBOM you have scanned. For more on running this kind of search at scale, see finding affected products fast with SBOMs.

If I installed a bad version, what do I rotate, and in what order?

Treat the machine as compromised, rotate from a different machine, and start with the credentials that let the attack spread. Both the CSA advisory and the independent analysis say to treat affected systems as compromised and rebuild where you can. The order matters because modern payloads install a backdoor that can capture a newly rotated key on the same host.

  1. Isolate first. Disconnect the machine or runner from the network, stop the job, and do not log in to anything new from it. For LiteLLM 1.107.0, the independent analysis describes an hourly backdoor process; rotating keys while it runs only hands over the new ones.
  2. From a clean device, revoke publishing and source control tokens. npm tokens, PyPI tokens, GitHub personal access tokens and GitHub CLI OAuth tokens, deploy keys, and CI tokens. These are what worms use to publish the next wave. Review recent npm and PyPI publishes under your accounts and recent pushes, new repositories and workflow changes in your GitHub organization. The CSA advisory says to watch for repositories whose description contains "Shai-Hulud: Here We Go Again".
  3. Rotate CI and CD secrets. Anything stored in your CI system that the job could read, including cloud deploy credentials and signing keys.
  4. Rotate cloud credentials. Access keys, service account keys, and any instance metadata credentials the host could reach. Review cloud audit logs for activity from unfamiliar addresses since the install time.
  5. Replace SSH keys and GPG keys. Generate new keys on a clean device, update authorized keys on servers and on your source control host, and remove the old ones.
  6. Rotate Kubernetes, Terraform and Vault credentials. Kubeconfig tokens, state backend credentials and anything in local configuration files.
  7. Rotate AI provider and SaaS API keys. LLM provider keys, AI coding tool tokens and keys in .env files. The independent analysis notes the October payload also targeted LLM keys stored in a LiteLLM proxy database.
  8. End browser sessions and change saved passwords. If the payload collected browser data, sign out of sessions and change passwords that were saved in the browser.
  9. Rebuild the machine or runner. Reimage the laptop or destroy the runner. Rebuild images from a known clean lockfile and redeploy.
  10. Write down the timeline. Install time, removal time, what ran and what you rotated when.

Our compromised dependency incident response playbook covers the communication and evidence side in more detail.

What is different about pip and uv compared with npm?

npm has one switch that stops install-time code. Python does not, because the risky code paths are different.

Risknpm, pnpm, Yarnpipuv
Code at install timeLifecycle scriptsSource builds (setup.py, build backends)Source builds
Off switch for install-time codeignore-scripts; off by default in pnpm 10+ and current Yarn--only-binary :all: refuses sdistsno-build refuses sdists
Code that runs later without importNo direct equivalent.pth import lines and sitecustomize.pySame as pip
Lockfile with hashesBuilt inNot built in; use hashed requirementsuv.lock, built in
Cooldownmin-release-age (npm), minimumReleaseAge (pnpm), npmMinimalAgeGate (Yarn)uploaded-prior-toexclude-newer

The practical Python policy has four parts:

# 1. Lock with hashes (pick one approach)
uv lock
pip-compile --generate-hashes -o requirements.txt requirements.in

# 2. Install from the lock only, wheels only, with a cooldown
uv sync --locked --no-build
pip install --require-hashes --only-binary :all: --uploaded-prior-to P3D -r requirements.txt

# 3. Install your own project without pulling unchecked dependencies
pip install --no-deps -e .

The --no-deps step comes straight from the pip secure installs guide, which says to install your project with it "to be safe".

The fourth part is the uncomfortable one: wheels-only and hashes do not stop a malicious wheel. The LiteLLM releases were wheels. Their payload ran through .pth files at interpreter start, not through a build. Hashes would have helped only if your lock was written before the bad release, and wheels-only did nothing. The defense that covers that case is the cooldown, plus a habit of reviewing lockfile diffs for new top-level .pth or sitecustomize.py files when you can.

If a package has no wheel for your platform and you must build it, use no-build-package (uv) or a targeted pip install for that one package, in a build container with no credentials mounted.

What should a team-wide install policy look like?

Individual settings help one laptop. A team needs the same protections enforced where installs actually happen: developer machines, CI, image builds and AI coding agents that run installs on your behalf.

The policy, in one page

  1. Every project has a committed lockfile with integrity hashes. CI fails if it is missing or out of date (npm ci, pnpm install --frozen-lockfile, yarn install --immutable, uv sync --locked, pip install --require-hashes).
  2. Dependency install scripts are off by default. An allowed list lives in the repository and changes go through review.
  3. A cooldown of three to seven days applies everywhere, with a written bypass for security fixes that names the advisory.
  4. Installs go through an internal registry proxy. The proxy applies the cooldown centrally, keeps a copy of every artifact you have used, and lets you block a version for the whole company in one place. Per the OpenSSF guidance, private registries should not fall back to the public registry for your internal names, and you should register your scope on the public registry so nobody else can.
  5. No unpinned installs in Dockerfiles or CI scripts.
  6. Installs run without secrets in the environment. Split dependency installation into its own step that has no cloud, registry publish or deploy credentials. Inject secrets only into the later steps that need them.
  7. Restrict network egress from build jobs so they can reach only your proxy.
  8. Check signatures and provenance where they exist. npm audit signatures verifies registry signatures and provenance attestations. The npm provenance documentation is clear that provenance does not guarantee a package "has no malicious code"; it tells you where and how it was built. A drop in provenance between versions is a signal worth blocking on, which is what pnpm's trustPolicy: no-downgrade does.
  9. Publish your own packages with trusted publishing and short-lived tokens. GitHub's npm roadmap moves publishing to 2FA, seven day granular tokens and trusted publishing (GitHub blog). PyPI recommends trusted publishers and phishing resistant 2FA.
  10. AI coding agents follow the same rules. An agent that runs npm install some-package or pip install on your behalf bypasses the lockfile just like a person would. Make it ask before installs, and make the same .npmrc and uv.toml settings apply in its environment. See whether AI coding agents add vulnerable dependencies for the evidence and controls.

A CI job that applies most of it

# .github/workflows/build.yml
name: build
on: [pull_request]
permissions:
  contents: read
jobs:
  install-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Install from lockfile, no scripts, with cooldown
        run: |
          npm config set ignore-scripts true --location=project
          npm config set min-release-age 3 --location=project
          npm ci
          npm rebuild esbuild --ignore-scripts=false
      - name: Verify registry signatures and provenance
        run: npm audit signatures
      - name: Test
        run: npm test

Note what is missing: secrets. The job has read-only repository access and nothing worth stealing; deployment runs in a separate job with its own narrowly scoped credentials.

Choosing your controls

ControlStopsDoes not stopCost
Lockfile plus npm ci or frozen installNew bad versions of existing dependenciesBad versions already in the lockfile; ad hoc installsLow
Cooldown of 3 to 7 daysFresh hijacked releases removed within daysSlow attacks that wait out the cooldown; malicious code that sits unnoticedLow, plus a bypass process
Install scripts offInstall-time payloads in npm packagesCode that runs on import; Python .pth payloadsLow to medium
Wheels only (--only-binary, no-build)Python build-time codeMalicious wheelsMedium for packages without wheels
Hash-checked installsTampered artifacts and silent substitutionA bad version hashed into the lockLow
Registry proxy with central blockingRepeat installs after a version is flagged; public fallback for internal namesThe first hours before anyone flags it, unless the proxy enforces a cooldownMedium
No secrets during installCredential theft from CITheft from laptops with keys on diskLow
Egress restrictionsExfiltration from build jobsLaptops; allowed destinationsMedium
Signature and provenance checksPackages not built where they claimA pipeline that was itself compromisedLow

On the last row: the independent analysis assesses that the October LiteLLM wheels were built by the release pipeline itself from attacker-modified source, so provenance would not have flagged them. It is still worth checking, because attacks that publish from a stolen token outside the real pipeline lose their attestation.

So, should I run npm install right now?

Use this as a quick decision guide.

What you are about to runIs it safe right now?What to do instead
npm ci or a frozen install from a lockfile you reviewedYes, as safe as it getsKeep scripts off and run it without secrets in the environment
npm install to add a new packageUsually, with a cooldownCheck the package and version first, confirm it is more than a few days old, read the lockfile diff
npm update or merging a fresh dependency updateWait a few daysLet the cooldown filter it, or review each bumped version
npx some-cli or a global installRiskierPin the version (npx some-cli@1.2.3) and run it in a container or throwaway environment
pip install some-package with no versionRiskierPin, hash, prefer wheels, apply uploaded-prior-to
Installing during an active, named incidentNot until you have checkedSearch your lockfiles for the named versions first, then install with a cooldown

To vet one package before adding it, see is this npm package safe to install and the checklist in npm package safety checks before you install. For the history of the worm family behind the 2025 and 2026 npm waves, see the Shai-Hulud npm supply chain attack explained.

Common mistakes

  • Treating the lockfile as a safety certificate. A lockfile generated during the bad window reproduces the bad version forever. Review lockfile diffs like code.
  • Setting ignore-scripts on laptops but not in CI or Dockerfiles. CI runners hold the most valuable secrets. Put the setting in the project .npmrc so it travels with the repository.
  • Assuming wheels are safe. Python malware in 2026 shipped as wheels and ran through .pth files at interpreter start.
  • Inspecting a suspect Python image by running Python inside it. That starts the interpreter and runs the payload. Scan the image or its SBOM instead.
  • Rotating keys on the infected machine. If a backdoor is running, it receives the new keys too. Rotate from a clean device.
  • Rotating cloud keys before publishing tokens. Worms spread through npm, PyPI and GitHub tokens. Cut those first.
  • A cooldown with no bypass. Teams then disable it entirely during the first urgent CVE and never turn it back on.
  • Letting AI coding agents install freely. An agent running pip install mid-task skips every control that lives in your CI pipeline.

Fix it without waiting for upstream

A cooldown and a lockfile protect you from brand new bad versions, but they also slow down the good ones, and the same habit of waiting leaves known vulnerable versions in place for months. Closing that second gap is how you make "install from the lockfile" mean "install something safe".

In incidents like this one, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

FAQ

Is it safe to npm install right now?

For most projects, yes, if you install from a committed lockfile with npm ci, keep dependency install scripts off and use a cooldown of a few days for anything new. The documented incidents of 2025 and 2026 were removed within hours, so the risk sits almost entirely in installing brand new versions.

Is pip install safe?

Not by default: source builds run code, and a wheel can drop .pth files that run at every Python start. Pin with hashes, prefer wheels, add a cooldown and install without credentials in the environment.

Does npm ci protect against supply chain attacks?

It protects against new malicious versions of dependencies already in your lockfile, because it installs exactly what the lockfile records and fails if package.json disagrees. It does not help if the lockfile already contains a bad version or if someone runs npm install new-package.

Should I use ignore-scripts in npm?

Yes, for most projects. Set it in the project .npmrc, then rebuild the few packages that genuinely need install scripts. pnpm and current Yarn already default to blocking dependency scripts.

What is npm min-release-age?

An npm configuration option that installs only versions that have been available for more than a given number of days. Secondary sources date it to npm 11.10. Set min-release-age=3 in .npmrc for a three day cooldown.

Will a dependency cooldown stop me getting security patches?

It will delay them unless you write down a bypass, used only for a version an advisory names as the fix.

How do I know if I installed a compromised npm package?

Take the exact versions from the advisory, run npm ls <package> --all in each project, search lockfiles across your checkouts, check global installs, clear and search caches, and scan built container images. If the version is present and was installed, treat the machine as compromised.

What should I rotate after installing a malicious package?

From a clean device: publishing and source control tokens first (npm, PyPI, GitHub), then CI secrets, cloud keys, SSH and GPG keys, Kubernetes and Terraform credentials, AI and SaaS API keys, and browser sessions. Then rebuild the machine or runner.

Are lockfiles enough to stop malicious packages?

No. A lockfile written during a bad window records the bad version, and npx, global installs and unpinned Dockerfile lines never use one.

Does npm provenance mean a package is safe?

No. npm's documentation says provenance does not guarantee a package has no malicious code. It shows where and how the package was built, which helps catch packages published from stolen tokens outside the official pipeline.

See how Safeguard would help

The checks in this post come down to two jobs: finding where a named package version lives across your machines, repositories and images, and replacing it with a safe one under human control. The demos below show those flows. When you are ready to try it on your own code, download the apps at safeguard.sh/download and read the CLI documentation.

This first demo sets up the Safeguard runner on your own machine, approves it with its device code, and then scans from the terminal. It is the same flow you would use in Step 6 above to scan a repository or a suspect container image locally instead of starting the image and risking the payload.

Click through it at your own pace, or open it full screen.

This second demo is a real Safeguard CLI session. You ask it to fix the lodash CVE in a repository; it reads the finding and the advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. Because every install command waits for your approval, you decide whether it runs, and you can check that your .npmrc cooldown and script settings are in place first.

Click through it at your own pace, or open it full screen.

The third demo is a real Android session: you send work to a computer running the CLI and approve the command on the phone with Allow once, Allow for turn or Deny. During an incident that means you can start the lockfile search from your phone and still see, and approve, each command before it runs on the workstation.

Click through it at your own pace, or open it full screen.

Quick check

Why did a wheels-only, hash-checked pip install not protect against the October 2026 LiteLLM release?

The malicious release was itself a wheel, and its payload ran from a .pth file at every Python interpreter start, not from a build step. Hashes help only if your lock predates the bad version. A cooldown of a few days would have skipped it, because PyPI removed it in under two hours.

After installing a compromised package, which credentials should you rotate first, and from where?

Publishing and source control tokens first (npm, PyPI and GitHub), because worms use them to spread, and always from a clean device, because a backdoor on the affected machine would capture the new keys.

Sources

Last reviewed: 22 October 2026. Package manager settings change between releases; check your tool's current documentation before rolling out a policy.

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.