A malicious npm or PyPI package runs with the full rights of whoever installed it, which usually means a developer laptop full of tokens or a CI runner full of secrets. These incidents move faster than a normal supplier compromise: an install script fires on npm install or pip install, credentials leave within seconds, and in worm campaigns the stolen publishing tokens are used to infect further packages within hours. This template is a playbook for one specific scenario: a dependency you installed (directly or transitively) has been reported as malicious. It covers dependency scoping across lockfiles, caches and images, host triage, credential rotation and registry actions. It is written for security operations, application security and platform teams. Adapt roles, tools and timings to [Organisation].
How to use this template
- Replace text in square brackets, such as [Organisation], [Incident manager] and [AppSec lead], with your names and contact routes, then approve the playbook through your normal policy process.
- Steps are numbered and owned. Steps marked Decision: need a recorded decision, the decision maker and the time.
- Examples use npm and PyPI terms. Map them to other ecosystems you use (RubyGems, crates.io, NuGet, Maven, Go modules); the steps are the same.
- This playbook is the package-specific branch of the Supply Chain Compromise Response Playbook. If the malicious code reached your build system or release path, run the CI/CD Pipeline Compromise Playbook in parallel. If you are the maintainer whose package was hijacked, sections 4 to 7 also apply to your publishing account.
- It follows the incident response life cycle in NIST SP 800-61 Rev. 3, which is organised around the CSF 2.0 Respond and Recover functions.
1. Scope and triggers
In scope: open source packages pulled from public registries into developer workstations, CI/CD runners, container images, serverless bundles and production hosts, including transitive dependencies, internal registry mirrors and proxy caches. Out of scope: vulnerabilities without malicious intent (use your vulnerability management process).
| Trigger | Typical source | ATT&CK |
|---|---|---|
| Package version reported as malware | OpenSSF Malicious Packages (OSV reports), GitHub malware advisories, registry takedown notice, vendor research blog | T1195.001 Compromise Software Dependencies and Development Tools |
| Government or CERT alert naming a campaign | CISA, NCSC or national CERT alert | T1195.001 |
| Install-time script behaviour on a host | EDR alert: node or python spawning shells, downloading runtimes or reading credential files during install | T1059.007 JavaScript, T1546.016 Installer Packages |
| Credential harvesting signal | Reads of ~/.npmrc, ~/.aws/credentials, .env files, cloud metadata requests from build jobs | T1552.001 Credentials In Files, T1552.005 Cloud Instance Metadata API |
| Exfiltration or token misuse | Unexpected public repositories, webhook endpoints, publishes you did not make, secret scanning alerts | T1567 Exfiltration Over Web Service, T1528 Steal Application Access Token |
Severity: Critical if the package ran on a CI runner with deployment or publishing secrets, or on an administrator workstation; High if it ran on any developer machine; Medium if it is present in a lockfile or image but evidence shows it never executed; Low if only a non-executed cached copy exists.
2. Roles and contacts
| Role | Responsibilities | Contact |
|---|---|---|
| [Incident manager] | Declares the incident, sets severity, owns decisions and the timeline | [Name, out-of-hours route] |
| [AppSec lead] | Scopes affected repositories, lockfiles, images and mirrors; confirms malicious versions | [Team queue] |
| [SOC and DFIR] | Host triage, EDR hunting, network and identity log review | [SOC queue] |
| [Platform engineering] | Blocks versions in the registry proxy, pauses pipelines, rebuilds runners and images | [On-call rota] |
| [Secret owners and NHI team] | Revoke and rotate every credential reachable from affected hosts | From the secrets inventory |
| [Package maintainers] | If one of your own packages was republished: deprecate, unpublish or yank, and publish a clean version | [Owners of record] |
| [Legal, privacy and communications] | Customer, regulator and open source community notices | [Contacts] |
3. Detection and triage
- Open the incident and record the package name, ecosystem, malicious versions, first publish time and the advisory or report identifier. Treat the earliest malicious publish time as the start of your hunt window.
- Find every reference. Search lockfiles across all repositories (
package-lock.json,yarn.lock,pnpm-lock.yaml,poetry.lock,uv.lock, pinnedrequirements.txt), including nested dependencies, as CISA advised for the Shai-Hulud campaign. Then search your registry proxy or artefact repository for cached copies, container image SBOMs and serverless bundles. - Find every execution. A package in a lockfile is exposure; an install is execution. Use CI job logs for installs in the window, EDR process trees on developer machines (package manager spawning
curl,sh, a new runtime binary or a credential-reading process) and proxy logs for registry downloads by host. - List reachable credentials per host. For each machine or runner that executed the package, list what it could read: npm and PyPI tokens, GitHub and GitLab tokens, SSH keys, cloud CLI credentials, CI secrets injected into the job,
.envfiles, browser sessions and password manager CLIs. This list drives section 5. - Check for spread. Look for package versions published by your accounts in the window, new repositories, new workflow files, new self-hosted runner registrations and new deploy keys. The Shai-Hulud npm worm used each stolen npm token to republish the victim's own packages and registered infected hosts as self-hosted runners for persistence.
- Preserve evidence before cleaning: the malicious tarball or wheel (from your proxy cache), CI job logs (they may expire), EDR telemetry, and a disk image of at least one affected workstation.
- Decision: confirm severity and whether to run the CI/CD playbook in parallel. Record who decided and when.
| Evidence source | Question it answers | Retention risk |
|---|---|---|
| Lockfiles and SBOMs | Which projects and images contain the version | Low: in source control |
| Registry proxy logs | Which hosts downloaded it, and when | Medium: check log retention |
| CI job logs | Which jobs installed it, with which secrets in scope | High: often 30 to 90 days, and may be public |
| EDR process and file telemetry | Whether install scripts ran and what they read | Medium |
| Registry, GitHub and cloud audit logs | Whether stolen tokens were used | Medium: export early |
4. Containment
- Block the versions in your registry proxy or artefact repository so no new install can fetch them, and purge cached copies after preserving one for evidence.
- Pause affected pipelines and any scheduled jobs that resolve dependencies without a lockfile.
- Disable install scripts on CI while the incident is live: set
ignore-scripts=truein the CI.npmrc. For npm 11,min-release-age(a number of days) orbefore(a date) limit installs to versions published before a cut-off, which stops freshly hijacked releases resolving. - Isolate executing hosts. Network-isolate developer machines through EDR; take ephemeral runners out of the pool; deregister self-hosted runners.
- Block known exfiltration endpoints named in the advisory at the proxy and firewall (CISA advised blocking webhook.site for Shai-Hulud) and review prior connections to them.
- Revoke the highest-risk credentials first: publishing tokens and source control tokens, because these enable spread. Revoke before you rotate; deleting a file does not invalidate a token.
- Decision: whether to freeze all dependency updates organisation-wide until scoping is complete.
5. Eradication
- Rebuild, do not clean. Reimage developer machines that executed the package and destroy affected runners and build caches. Install scripts can drop persistence outside the project directory.
- Rotate every credential on the section 3 list, owner by owner, using the Secrets Rotation Runbook Template. Order: package registry tokens, source control tokens and SSH keys, CI secrets, cloud keys, SaaS API keys, then everything else on the host.
- Remove attacker persistence found in section 3: rogue repositories, workflow files, runners, deploy keys, OAuth and GitHub App grants, webhooks and cloud identities created with stolen keys. CISA's guidance included removing unnecessary GitHub Apps and OAuth applications and auditing repository webhooks and secrets.
- Pin to a known good version in every affected project, regenerate lockfiles, and rebuild images from clean bases.
- If you maintain an affected package: on npm you can unpublish within 72 hours if nothing depends on it, otherwise use
npm deprecateand publish a clean higher version (a name@version can never be reused). On PyPI, yanking hides a release from normal resolution (except exact==pins), and PyPI administrators can quarantine a project so pip cannot install it. Report the malware through the registry's own process.
| Credential found on host | Revoke action | Replacement |
|---|---|---|
| npm token | Revoke in npm account settings; check for publishes made with it | Trusted publishing (OIDC); granular tokens only where needed. See the npm Tokens Security Guide |
| PyPI API token | Remove in account settings; check release history | PyPI trusted publishing from CI |
| GitHub PAT or OAuth token | Revoke; review security log for new keys and apps | Fine-grained, short-lived tokens. See the GitHub Personal Access Tokens Security Guide |
| Cloud access keys | Deactivate; review CloudTrail or activity logs from the hunt start | Workload identity federation or SSO-issued short sessions |
| CI secrets | Rotate at source; delete unused secrets | OIDC to cloud; secrets scoped to the steps that need them |
6. Recovery
- Re-enable pipelines one at a time, with install scripts off by default and an allow-list for packages that genuinely need them.
- Return rebuilt workstations to users with fresh credentials and confirmed phishing-resistant MFA on registry and source control accounts.
- Monitor for use of any revoked credential (failed authentications show what you missed) and for new publishes from your organisation's accounts for at least 30 days.
- Decision: criteria to close: all executing hosts rebuilt, all listed credentials rotated, no attacker persistence found, dependency freeze lifted.
7. Communication and notification
| Audience | Owner | When | Content |
|---|---|---|---|
| Developers | [AppSec lead] | Within the first hour | Stop installs, do not run the package, report if installed, expect credential resets |
| Executives | [Incident manager] | At declaration and daily | Scope, credentials at risk, customer impact |
| Downstream users of your packages | [Package maintainers] | As soon as a clean version exists | Affected versions, safe version, indicators, rotation advice |
| Registries and community | [AppSec lead] | On confirmation | Malware report with file hashes; OSV-format report where you have one |
| Customers and regulators | [Legal and privacy] | Per contract and law | If stolen credentials reached personal or customer data |
8. Post-incident review
- Hold the review within [10] working days and build a timeline: malicious publish, first install in [Organisation], detection, revocation, last rotation.
- Ask: how long did it take to find every lockfile and image containing the version? Did CI logs still exist? Which credentials had no owner?
- Track what made the blast radius large: long-lived tokens on laptops, secrets in the environment of dependency install steps, install scripts enabled by default.
- Feed changes into dependency policy, CI hardening and this playbook. Record the version change below.
Implementation guidance
Prepare before the incident. The steps above are only fast if the groundwork exists:
- Single route to registries: force all installs through a registry proxy so blocking and download logs exist.
- Searchable lockfiles and SBOMs for every repository and image, so scoping takes minutes, not days.
- Malware feeds: subscribe to the OpenSSF Malicious Packages dataset (OSV format) and your platform's malware advisories, and match them automatically against lockfiles.
- Install hygiene:
npm ciwith committed lockfiles,ignore-scriptson CI, a release-age cool-off for new versions, and pip hash-checking mode for Python builds. - Publishing identities: move your own packages to trusted publishing and set npm publishing access to require two-factor authentication and disallow tokens. npm revoked all classic tokens on 9 December 2025 and caps granular write tokens at 90 days, so legacy automation may already be broken or due to expire.
- Secret-free installs: fetch secrets only in the steps that use them, never in the job environment during dependency installation.
Small vs large organisations. A small team can run this with one incident manager and the developers themselves, provided there is a list of who holds which tokens; prioritise trusted publishing and a release-age cool-off. A large organisation needs automated lockfile search across thousands of repositories, a registry proxy with block lists, and pre-agreed rotation owners; its bottleneck is usually credential ownership, not detection.
| Framework | Mapping |
|---|---|
| NIST CSF 2.0 | DE.CM-09, DE.AE-02, RS.MA-01, RS.AN-03, RS.MI-01, RS.MI-02, RC.RP-01, GV.SC-07 |
| NIST SP 800-53 Rev. 5 | IR-4 Incident Handling, IR-6 Incident Reporting, SR-3 Supply Chain Controls and Processes, SR-4 Provenance, SR-11 Component Authenticity, SI-7 Software, Firmware, and Information Integrity, CM-7 Least Functionality |
| MITRE ATT&CK | T1195.001, T1059.007, T1546.016, T1552.001, T1552.005, T1528, T1567 |
Readiness metrics: time from advisory to complete list of affected repositories and images; time from detection to revocation of publishing tokens; percentage of CI jobs with install scripts disabled; percentage of your packages on trusted publishing; percentage of reachable credentials rotated within 24 hours.
Common failure patterns:
- Scoping direct dependencies only, missing the transitive copy that actually ran.
- Rotating the npm token but not the cloud keys and SSH keys on the same laptop.
- Cleaning a workstation instead of rebuilding it.
- Waiting too long: in the Shai-Hulud second wave, Wiz found more than 60% of leaked npm tokens still valid a week later.
- Losing CI logs to retention before the hunt reaches them.
References: NIST: SP 800-61 Rev. 3 Incident Response Recommendations · CISA: Widespread Supply Chain Compromise Impacting npm Ecosystem · OpenSSF: Malicious Packages · npm Docs: Trusted publishing · npm Docs: Unpublish policy · PyPI Docs: Yanking · PyPI Blog: Project quarantine
Version history: v1.0 [date], approved by [Approver]. Review every [12] months or after each incident.
Related NHI Mgmt Group resources: Supply Chain Compromise Response Playbook · CI/CD Pipeline Compromise Playbook · npm Tokens Security Guide · Leaked Credential Response Playbook