Guide

You installed a malicious package. What now?

The instinct is to uninstall the package and move on. That is the one response that reliably does not work, because the package already ran. Here is a more useful order of operations.

Updated · Precursor Security

First: assume execution already happened

The large majority of malicious packages we track execute at install time, not at import time - a postinstall hook on npm, a setup.py or build hook on PyPI. That matters because it changes what you are dealing with.

You are not asking "is this code reachable from my application". You are asking "what could a process running as me, on this machine, with this environment, have taken". A cancelled install, a failed build, a container that was thrown away thirty seconds later - all of them still ran the payload.

The order that actually matters

Work outwards from the most valuable and most reusable credentials.

  1. Rotate registry tokens first. An npm or PyPI publish token is the one credential that turns your compromise into someone else's. This is precisely how single-package incidents become fifty-package worms. Do this before anything else.
  2. Rotate cloud and CI credentials. AWS keys, GCP service accounts, GITHUB_TOKEN, CI secrets, anything that was in the environment. Assume every environment variable readable by that process is now public.
  3. Rotate developer credentials. SSH keys, GPG keys, and any .npmrc, .pypirc, .aws/credentials, or .env file on the machine. Several campaigns specifically hunt these paths.
  4. Then deal with the package itself - pin to a safe version, reinstall from a clean lockfile.

Do the rotation from a different machine. If the payload installed a persistent credential stealer, rotating from the compromised host just hands over the new credentials too.

Clear the caches - this is the step people skip

Public takedown does not reach your infrastructure. After the registry removes a malicious version, these will happily keep serving it:

  • Private mirrors and proxies: Artifactory, Nexus, Verdaccio, devpi, AWS CodeArtifact, Google Artifact Registry
  • CI caches keyed on lockfile hash - the compromised tarball is in the cache, and the hash still matches
  • Docker layer caches containing the install step
  • Local ~/.npm and ~/.cache/pip

We see this repeatedly: an organisation cleans up, then reinfects itself a week later from its own cache. Purge the specific package versions from every one of these before you rebuild.

Work out the blast radius

Once the bleeding has stopped, scope it:

  • Which builds ran? Check CI history for every job that installed the affected version. Each one is a separate exposure with its own secret set.
  • Which developers pulled it? Anyone who ran install while the version was in the lockfile, not just whoever added it.
  • What egress happened? If you have DNS or network logs from build agents, look for connections to unfamiliar hosts around the install timestamps. Many incident write-ups list specific indicators of compromise worth grepping for.
  • Did anything publish? If a registry token leaked, check whether any of your own packages were published during the window.

What is not worth your time

  • Reading the payload first. Understanding the specific malware is interesting, but it is slower than rotating and rarely changes the answer. Rotate first, read later.
  • Deciding it was "only a dev dependency". Dev dependencies install on developer laptops and CI agents, which is where the credentials are. The production/dev distinction does not protect you here.
  • Hoping the version range saved you. If the compromised version satisfies your range and you installed during the window, you got it. Check the lockfile rather than reasoning about the range.

Common questions

Do I need to wipe the machine?
For most install-time credential stealers, rotating credentials and clearing caches is proportionate. Rebuild if the advisory describes persistence - a background daemon, a scheduled task, a modified shell profile - or if the host handles anything sensitive enough that you would rather not reason about it.
Nothing looked broken. Was I actually affected?
Almost certainly you would not notice. These payloads are written to be silent - the install succeeds, the build passes, and the exfiltration is a single HTTPS request among many. Absence of symptoms is not evidence.
How do I find every project that has it?
Scan the lockfiles rather than trusting memory. Transitive dependencies are the common case, so a repo can be affected without the package appearing anywhere in its package.json.

Check your own dependencies

Paste a lockfile and see whether any of this applies to you. It runs entirely in your browser - nothing is uploaded.

Check a lockfile now

Related guides