You run a scan and get a HIGH severity finding in minimist@1.2.0. You grep your package.json — it isn't there. So why is it in your build?
Direct vs transitive
A direct dependency is one you declared yourself, in package.json, requirements.txt, pom.xml, or whatever manifest your ecosystem uses. A transitive dependency is one of those packages' own dependencies — and their dependencies' dependencies, recursively, however deep the chain goes. A typical modern JavaScript project has a handful of direct dependencies and hundreds of transitive ones. minimist is a good example: it's an argument parser almost nobody installs directly, but it sits underneath dozens of popular build tools, so it shows up in an enormous number of dependency trees without anyone choosing it.
This matters because the fix is different depending on which one you have. A vulnerable direct dependency: bump the version yourself. A vulnerable transitive dependency: you can't npm install minimist@latest and expect it to matter — you have to find which of your direct dependencies pulls it in, and bump that one (or use your package manager's override mechanism) instead.
Why lockfile "depth" is not the same as "distance from root"
If you've looked at a package-lock.json, you might assume the nesting level of node_modules/ tells you how many hops a package is from your project. It doesn't, reliably. Package managers hoist dependencies — they lift a package as high in the node_modules tree as they can without creating a version conflict, purely to save disk space and installation time. A package can be physically hoisted to the top level of node_modules (nesting depth 0) while still being a transitive dependency three levels deep in your actual logical dependency graph. Depth-in-the-filesystem and depth-in-the-dependency-graph are two different numbers, and conflating them is a common source of confusion when triaging findings.
Tracing a package back to its root
The question that actually matters when you see a vulnerable transitive package is: which direct dependency do I need to upgrade to make this go away? Answering that requires walking the real dependency graph — the parent-to-child edges recorded in the lockfile — backward from the vulnerable package until you reach something declared in your manifest.
DepWarden builds this graph from your lockfile (npm, yarn, pnpm, Maven's effective POM resolution, Go's module graph, Cargo's resolver output, and more) and exposes it two ways. The dependency graph view shows the whole tree so you can see how deep and wide your third-party surface actually is. The dependency tree / trace-to-root search lets you type a package name and instantly see every direct dependency that pulls it in, with the actual path — so "why is this even installed" has a concrete answer instead of a guess.
What to actually do about it
Once you know the path, there are three real options: upgrade the direct dependency to a version that itself pulls a fixed version of the transitive package; use your package manager's override/resolution field (overrides in npm, resolutions in Yarn, dependency management in Maven) to force the transitive version directly; or, if neither is available yet, treat it as accepted risk with an expiring exception rather than silently ignoring it. DepWarden's minimal-fix solver checks the first option automatically — for a given transitive CVE, it tells you which direct dependency to bump and whether the latest release of that dependency actually resolves the issue, so you're not upgrading blind.
Related: software composition analysis, CVSS, EPSS and KEV explained, a free Snyk alternative.
Top comments (0)