Back to publishing & release Automate semantic versioning Manage release channels Harden the supply chain

Responding to a Compromised Dependency

Sooner or later a package in your tree will be compromised: a maintainer's account taken over and a malicious version published, a self-propagating worm that steals tokens and republishes packages, or a dependency that turns hostile. The September 2025 compromise of widely used packages such as chalk and debug, after a maintainer was phished, showed how quickly a bad version can spread — millions of weekly downloads within hours. When it happens, speed and order matter more than perfect information: find out whether you installed the bad version, stop it spreading, rotate anything it could have stolen, and restore a known-good state. This guide is a runbook for those first hours, plus the preparation that makes it fast.

Triage: are we affected?

An advisory typically names the package and the malicious versions, for example debug@4.4.2. The first question is whether any lockfile, build or deployed artefact contains those versions. The broader controls are covered in Supply-Chain Security Hardening.

# Every repository: is the bad version in the lockfile?
grep -n '"node_modules/debug"' -A2 package-lock.json | grep '"version": "4.4.2"'
grep -n "debug@4.4.2" pnpm-lock.yaml yarn.lock 2>/dev/null

# Why is it there, and through which dependency?
npm ls debug
pnpm why debug -r

# Installed right now on this machine or image?
find / -path "*node_modules/debug/package.json" -exec grep -l '"version": "4.4.2"' {} + 2>/dev/null

Search across all repositories with your code-search tool for the package name and version in lockfiles. If you keep SBOMs for releases, query them for the affected versions — covered in Generating an SBOM for a JavaScript Package.

Deciding how exposed you are A chain of checks from whether the bad version is in any lockfile, to whether it was installed during the exposure window, to whether install scripts ran and whether it shipped in a release. Bad version in no lockfile or SBOM? Not affected (so far) pin away from it and monitor yes Installed but scripts blocked, not shipped? Limited exposure clean caches, update lockfile yes no Install scripts ran on dev or CI? Credential exposure rotate everything the machine held yes no Shipped in a release users exposed: incident response + advisory no
Exposure depends on whether the version was installed, whether its code ran, and where.

Look at the exposure window too: the time between the malicious publish and its removal. A lockfile that pinned an older version protected you unless someone installed or updated during the window. Dependency bots are a common path in — an automated update pull request can pull the bad version and run CI with it.

Containment

Stop new installs of the bad version everywhere at once:

  1. Pin away from it with overrides so no install anywhere can resolve it, even through a transitive range:
{
  "overrides": {
    "debug": "4.4.1"
  }
}

pnpm uses pnpm.overrides (or overrides in pnpm-workspace.yaml) and Yarn uses resolutions. Overrides are covered in Pinning Vulnerable Transitive Versions with overrides.

  1. Pause dependency automation. Disable auto-merge and pause Renovate or Dependabot until the incident is understood.
  2. Block the version at your registry proxy, if you use one, so no machine in the organisation can download it even from an unpatched repository.
  3. Purge caches that may hold the tarball: CI package caches, Docker layers built during the window, and proxy caches.

Credential rotation

Most modern npm malware targets secrets: npm and GitHub tokens, cloud credentials, SSH keys, environment variables in CI. If the malicious code could have run — an install script on a developer machine or CI runner, or runtime code in a test — assume every secret available to that process is compromised:

  • npm tokens on affected machines and in affected CI jobs: revoke and reissue, or better, move to trusted publishing.
  • Source control tokens, including CI-provided tokens with write permissions.
  • Cloud credentials in environment variables or configuration files.
  • Secrets injected into the affected CI jobs.

Then check for persistence and lateral movement: unexpected new versions of your packages (worms republish packages using stolen tokens), unfamiliar repositories or workflow files created in your organisation, and new SSH keys or tokens on accounts.

Order of operations in a dependency compromise Identify exposure, contain installs, rotate credentials, check for persistence, rebuild from clean state, then communicate and review. identify exposure lockfiles, SBOMs, install logs which repos, machines, releases contain overrides, pause bots, block at proxy rotate credentials npm, git, cloud, CI secrets assume anything reachable is stolen check persistence own packages, workflows, keys rebuild and release clean runners, verified lockfile
Contain first so the blast radius stops growing, then rotate everything the malicious code could reach.

Investigating what the malicious code did

Advisories usually describe the payload — what it reads, where it sends data, whether it persists — and that description should drive the scope of your response. When details are thin, a few sources of evidence help.

CI logs show whether the malicious version's install script ran (look for its lifecycle script output) and which secrets were in the job environment at the time. Many CI systems keep logs for a limited period, so export the relevant runs early.

Network egress logs from CI runners, proxies or cloud VPC flow logs can show connections to the exfiltration endpoints named in the advisory. A match is strong evidence that data left; the absence of a match is useful but not conclusive if the payload used common hosts such as public paste or webhook services.

Registry and source-control audit logs show whether stolen credentials were used: new tokens created, packages published, repositories or workflow files created, or unusual API activity from unfamiliar IP addresses. npm shows recent publishes per package; GitHub organisation audit logs show token and workflow events.

Developer machines that ran the install need the same scrutiny as CI runners. Ask developers to check their shell history and install logs for the affected window, and treat their local credentials — SSH keys, cloud CLI credentials, npm and GitHub tokens in dotfiles — as exposed if the script ran.

Communication during the incident

Keep a single incident channel and a written timeline from the first message: when the advisory arrived, what was found where, which credentials were rotated and when. Internal teams need clear instructions — pause merges of dependency updates, do not run installs on the affected branches, rotate personal tokens if they installed during the window. If your own released software was affected, prepare an external notice early with the affected versions, the fixed version and any actions users must take; publishing it quickly limits downstream damage more than a perfectly worded notice later.

Recovery

Rebuild affected artefacts from a clean state: fresh CI runners (not reused self-hosted runners without cleaning), a lockfile that resolves only known-good versions, and caches purged. If a release shipped the malicious code, publish a fixed release and tell users what to do — which versions to avoid, which credentials to rotate — and, for published packages, deprecate the affected versions of your package with a clear message, as covered in Deprecating npm Package Versions.

Preparation that makes this fast

Most of the response time goes to answering "where is it?" and "what could it reach?". Prepare the answers in advance:

Preparation versus response time Maps preparation measures to the response step they speed up and the risk they reduce. speeds up reduces SBOMs for every release finding affected releases missed exposures committed lockfiles + frozen installs knowing exact versions silent upgrades install scripts blocked by default scoping exposure code execution at install trusted publishing, no stored tokens rotation stolen publish rights registry proxy with blocklists containment org-wide spread
Every hour spent on preparation removes several hours from an incident.

Also: keep a written runbook with owners for each step, practise it once on a simulated advisory, and make sure you can list every repository's lockfile quickly with your code-search tooling. Blocking install scripts by default — covered in Blocking Malicious Install Scripts with --ignore-scripts — turns many incidents from "credentials stolen" into "bad version downloaded, never executed".

Worked example: a Monday advisory

An advisory lands at 09:00: two packages deep in the tree had malicious versions published over the weekend, with an install-time payload that steals npm and cloud tokens. By 09:30 the team's code search shows the versions in two repositories' lockfiles, both from a Renovate pull request opened on Saturday; its CI run executed the install script. Overrides are merged across all repositories by 10:00, Renovate is paused, and the proxy blocks both versions. The CI secrets for those two repositories are rotated; the npm token found in one job is revoked, and the team confirms no unexpected versions of their own packages were published. Neither pull request had merged, so no release shipped the code. The post-incident review moves the two repositories to trusted publishing and turns off install scripts in CI.

Frequently Asked Questions

Is deleting node_modules enough? No. Deleting the package removes the code, not its effects. If it ran, the secrets it could reach must be treated as stolen.

Should we pin every dependency exactly to prevent this? Exact pins plus committed lockfiles prevent silent upgrades, but you still need updates. Pair frozen installs with delayed, reviewed updates — many teams wait a few days before adopting new releases, which avoids most short-lived malicious versions.

What if our own package was compromised? Revoke all publish credentials, remove the malicious versions (npm support can help for recent publishes), publish a clean version, deprecate the bad ones, and publish an advisory with indicators and remediation steps.

How do we know when it is safe to resume dependency updates? When the malicious versions are removed from the registry, your overrides and proxy blocks are in place, and the maintainers have published a clean version you have reviewed. Resume automation with a delay on new releases for a while, and keep the overrides until the clean version is adopted everywhere.

Related

Supply-Chain Security Hardening