Guide · dependency scope
Are dev dependencies a security risk?
Short answer: yes, but not the same risk. A vulnerable test runner is never reachable by someone attacking your running service, because it is not in the artifact you deploy. It is executed on a build agent that usually holds registry credentials and deploy tokens. So the threat is compromise of your build, not exploitation of your product — which makes dev dependencies lower priority, and not zero priority.
Published 10 Sep 2026 · by the depproof team
How much of your audit output is this?
More than most people expect. We scanned 24 public repositories across five ecosystems and counted every finding a build gate would see. 479 of 1,203 — two in five — were on dependencies their own manifest declares as development-only. The share was highest in the severities that actually interrupt people: 36% of criticals and 47% of highs.
It varies enormously by ecosystem, and that variation is more useful than the average.
- npm — 57.6% of findings were development-scoped. JavaScript build toolchains are deep, and a bundler or test runner pulls in thousands of packages your application never touches.
- Python — 20.7%. Dev groups exist in Poetry, PDM and uv, and are used less heavily.
- Gradle — 19.0%, with a further 19 findings where the build file declared no scope at all.
- Maven — 0% in this sample. Maven states scope reliably; these particular projects simply had no vulnerable test dependencies.
- Go — 0%, and it cannot be otherwise.
go.modrecords no scope, so nothing can distinguish a test-only module from a production one.
Method, limits and the full data: Findings that never ship. Measured across 24 pinned public repositories on 10 September 2026, counting each finding once per manifest a build gate would evaluate. The headline share moved from 54% to 40% across three runs as our sample grew more diverse, so treat any single figure as a property of the estate it was measured on.
How to see only what ships
Every package manager can produce the production-only view. These read the same lockfile you already have and report what a production install would contain.
# npm — production tree only
npm audit --omit=dev
# pnpm
pnpm audit --prod
# Yarn Berry
yarn npm audit --environment production In the JVM and Python worlds the scope is declared in the build file rather than passed on the command line, so a scanner reads it directly.
<!-- Maven: never packaged, available to the test compiler -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
Gradle uses testImplementation and testRuntimeOnly. Poetry, PDM
and uv all support dependency groups. Go has no equivalent, so any tool offering to filter
Go dependencies by scope is guessing.
Why you should not filter by default
It is tempting to add --omit=dev to CI and move on. Resist it, for one
concrete reason.
In the sample above, one finding on the CISA Known Exploited Vulnerabilities catalogue was development-scoped — and rated only medium. A severity filter would have dropped it before a scope filter got the chance. Something actively exploited in the wild, running on a machine that holds your signing keys, invisible to both of the filters a team is most likely to configure.
A filter that runs before a human has looked will eventually hide something that mattered, and nothing will say so. Scope belongs in the ranking, not in a gate that silences findings nobody read.
What to actually do
- Fix production findings first. They are reachable by people attacking your service.
- Do not let dev findings block a release. They are not exposed to your users, and treating them as equal is how a gate gets switched off entirely.
- Escalate two things regardless of scope: anything on the CISA KEV catalogue, and anything that can execute code at install or build time.
- Check what is really in the artifact if a bundler, a monorepo or a multi-stage Dockerfile is involved. Those are the three ways a dev dependency reaches production despite the manifest.
- Watch the undetermined count. If your tool reports zero dependencies of unknown scope, on a Go or version-catalogue project, it is asserting something the input does not contain.
Questions people ask
Are dev dependencies a security risk?
Yes, but a different risk from production dependencies, and the difference decides how urgently you act. A development dependency — a test runner, a build plugin, a linter — is not part of the artifact you deploy, so a vulnerability in it cannot be reached by an attacker hitting your running service. It is still executed, on a build agent that usually holds registry credentials, signing keys and deploy tokens. So the realistic threat is supply chain compromise of your build, not exploitation of your product. That makes dev dependencies a lower priority than production ones and not a zero priority, which is why the right handling is to rank them lower rather than to hide them.
Should I fix vulnerabilities in devDependencies?
Fix them, but after your production ones, and do not let them block a release. A vulnerability in a test framework is not exposed to your users. Two cases deserve immediate attention regardless of scope: an advisory on the CISA Known Exploited Vulnerabilities catalogue, because someone is actively using it, and anything that could let a dependency execute arbitrary code at install or build time, because that reaches your CI credentials. In our own measurement of 24 public repositories, one known-exploited finding was development-scoped and rated only medium, so a severity filter would have dropped it before scope ever came up.
How do I exclude dev dependencies from npm audit?
Run npm audit --omit=dev. On pnpm use pnpm audit --prod, and on Yarn Berry use yarn npm audit --environment production. Each reads the same lockfile and reports only what your production install would contain. Treat the result as a priority view rather than the truth: the findings you just hid still describe code that runs on your build agents, so the honest workflow is to look at the full list, fix production first, and schedule the rest.
Do dev dependencies end up in production?
Normally no, and that is the whole point of the distinction. npm install --omit=dev, a production Docker build, a Maven package at test scope, or a Gradle testImplementation configuration all exclude them from the shipped artifact. The exceptions are worth knowing: a bundler can inline code from a dev dependency into your build output, a monorepo can promote a dev dependency of one package into the runtime tree of another, and a Dockerfile that copies node_modules from a build stage without pruning ships everything. If you are unsure, check what is actually in the artifact rather than what the manifest declares.
Why does npm audit report so many vulnerabilities in dev dependencies?
Because JavaScript build toolchains are enormous and deep. A test runner, a bundler and a linter can pull in thousands of transitive packages that never touch your application code. We measured this across 24 public repositories in five ecosystems: 57.6% of npm findings were on development-scoped dependencies, against 20.7% for Python and 19.0% for Gradle. If your audit output feels dominated by tooling you do not recognise, that is an accurate reflection of the ecosystem rather than a problem with your project.
What is the equivalent of devDependencies in Maven, Gradle and Go?
Maven uses the test scope in the dependency declaration, and provided for things the runtime container supplies. Gradle uses the testImplementation and testRuntimeOnly configurations. Both are read straight from the build file. Go has no equivalent: go.mod records no scope at all, so a scanner cannot tell a test-only module from a production one, and any tool claiming to filter Go dependencies by scope is inferring rather than reading. Gradle projects built on a version catalogue also declare no scope in the catalogue itself, so scope filtering gives them nothing.
Should a security tool hide development findings by default?
No. A filter that runs before a human has looked will eventually hide something that mattered, and nobody will know it happened. The safer design is to report every finding, record which ones ship and which do not, and use that to order the work. depproof ships a scope filter and leaves it off by default for exactly this reason, and reports how many dependencies declared no scope at all rather than assuming they ship.
Getting the scope view for your own estate
depproof reads the declared scope of every dependency in every ecosystem that states one, reports what ships and what does not, and tells you how many it could not determine rather than assuming they are safe. The suppression filter exists and is off by default, for the reason above.
Related reading: scanning npm projects · pnpm audit not working · why zero findings is not zero risk