When a compromised package turns out to be a dev dependency, it never shipped, but it may have run on laptops and CI runners with real credentials. This tabletop practices the argument that follows: not in production does not mean not exposed. Run it in 60 minutes with four injects.
The misconception to test
A common first reaction is relief: it is a linting or testing tool, so customers are not affected. That can be true for shipped code and false for everything else. Dev dependencies run where your secrets are: on developer machines, in build jobs, and in the pipeline that signs and publishes releases.
Install scripts are one reason. The npm configuration documentation states that the ignore-scripts setting defaults to false, so lifecycle scripts run unless you change it. The scenario does not depend on a specific package manager, but it is a useful fact for the discussion.
Setup
Invent a fictional build plugin, fastlint-plugin, used by six repositories. Write down real facts before the session: which pipelines install dev dependencies, what secrets those jobs can read, whether release signing happens in the same job, and whether developer machines keep long lived tokens.
Injects
| Time | Inject | Question |
|---|---|---|
| 0 min | An advisory says versions 4.2.1 to 4.2.3 of the plugin were published by an attacker | Do we use it? Where? |
| 10 min | Inventory shows it in six repos, all as dev dependencies, none in production images | Is this a customer issue? |
| 25 min | CI logs show version 4.2.2 ran in the release job for service C three days ago | What did that job have access to? |
| 40 min | A developer says they installed it on a laptop last week | What do they need to do, and who checks? |
Discussion questions
- Which credentials were present in the release job for service C? Which could publish or sign?
- Could the plugin have modified build output? How do you compare artifacts built before and after the bad version appeared?
- What do developers do with a laptop that ran the install? Who decides whether to reimage?
- Do you rebuild and republish releases built during the window?
- What would you tell customers if the answer to question 2 is "we cannot tell"?
What good looks like
| Area | Strong answer |
|---|---|
| Scope | Build and developer machines treated as in scope, with a list of affected jobs and people |
| Credentials | Secrets present in affected jobs rotated, starting with those that can publish or sign |
| Artifacts | Releases built in the window rebuilt from a clean state and compared where possible |
| People | Developers given a short, clear checklist, not a vague warning |
| Fixes | Separate build and signing stages, short lived tokens, install scripts disabled where tests allow |
Common traps
- Closing the incident when production is confirmed clean.
- Rotating just the secrets that were obviously used, not all that were readable.
- Forgetting caches on runners that may still hold the bad package. See purging CI caches after a compromise.
- Skipping the laptop question because it feels awkward.
Quick check
Is a dev dependency compromise a customer incident?
Not automatically. It becomes one if the package could have changed shipped artifacts or exposed credentials used to publish them. The exercise is about finding out.
Do install scripts run for dev dependencies?
Generally yes, since they install like any other package unless scripts are disabled. Check your own configuration.
Safeguard can help with the inventory side of this work. SBOM and dependency scanning show where a package and version appear across your projects, and policies and gates can stop a flagged version at the pull request. You can try a free scan at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.