When developers think about dependency security, they typically think about the packages they installed. Open package.json or requirements.txt and the risk surface seems manageable. But the dependency graph your package manager actually resolves is a different animal. A Node.js project with 30 direct dependencies can have 600 or more transitive entries in package-lock.json. Most of those packages are never audited, and most CVEs land in them.
That disproportion is not incidental. It's structural. Direct dependencies are chosen deliberately, evaluated sometimes, updated occasionally. Transitive dependencies accumulate invisibly with every npm install. The developer who added express to their project in 2022 did not consciously decide to take on qs, path-to-regexp, or the half-dozen other packages that came with it. Those just appeared in the lockfile, and they stayed there.
Why the Lockfile Is Both Protection and Blind Spot
A lockfile is an integrity mechanism. It pins the full resolved graph, including every transitive, to exact versions. That means a build on day 100 uses the same package versions as the build on day 1, which is genuinely useful for reproducibility.
But pinning does not prevent CVEs from being published against the versions you've pinned. When a vulnerability surfaces in a transitive package that your lockfile has frozen at version 2.1.0, you're now running known-vulnerable software with a false sense of stability. The lockfile is doing exactly what it's designed to do, and you're still exposed.
The deeper issue is that lockfiles obscure the source of risk. Open a package-lock.json for a mid-size project and you'll see a flat list of every resolved package. Without tooling, it's very difficult to trace which of your direct dependencies pulled in which transitive, which makes it hard to know whether removing a CVE-bearing transitive requires upgrading one direct dependency or five.
The Attack Surface You Didn't Know You Were Accepting
Supply chain attacks in the last few years have mostly targeted transitive packages, not direct ones. The pattern is consistent: a package deep in the graph gets compromised, either through a maintainer account takeover or a deliberately introduced vulnerability in an update, and because nobody is watching that package's security status, the compromise goes undetected.
Consider a plausible scenario: a mid-size engineering team at a SaaS company in the Chicago area runs a Python monorepo with around 180 services. Their direct dependency count is carefully managed. Their transitive count, when we do a full graph resolution, is something like 4,000 unique packages across the estate. Their security tooling is scanning the direct layer. The transitive layer is effectively unmonitored. When a CVE is published against a serialization library three levels deep in their data pipeline, they don't find out until a downstream vendor flags it in a partner security review three months later.
That scenario is not unusual. It's the default state of most engineering organizations that haven't invested specifically in full-graph SCA (software composition analysis).
What Full Graph Analysis Requires
Analyzing transitive dependencies accurately requires resolving the full dependency graph per ecosystem, not just parsing the manifest. This is non-trivial. npm, pip, Maven, and Cargo all have different resolution strategies, version conflict rules, and lockfile formats. A scanner that only parses requirements.txt without resolving the full pip dependency tree will miss most transitives.
Repohelm resolves the full graph per repository, per ecosystem, before doing any CVE matching. We use the resolved lockfile as the primary source of truth, then cross-reference against OSV (Open Source Vulnerabilities) and NVD to identify CVE-bearing packages at any depth. The resulting report shows not just which packages are vulnerable but the dependency chain that brought each one in: your direct dependency, then each intermediate, then the vulnerable transitive at the bottom.
That chain is operationally important. It tells you whether the fix is to bump a direct dependency (which may or may not upgrade the transitive), or whether you need to override a transitive version explicitly. Not all package ecosystems support explicit transitive overrides cleanly. npm's overrides field and pip's constraints files work differently and have different limitations.
Reachability: Not All Transitive Vulnerabilities Are Equal
We want to be precise about scope here: not every CVE in a transitive package represents actionable risk. A vulnerability in a package that your code path never executes is materially different from one in a hot path your API calls on every request.
Reachability analysis attempts to determine whether vulnerable code is actually called by your application. Current static reachability tools are imperfect, especially for dynamic languages like Python or JavaScript, but they're useful enough to significantly reduce noise. A CVSS 7.5 vulnerability in a testing utility that never runs in production is not the same priority as a CVSS 7.5 in your HTTP parsing library.
When Repohelm opens a PR for a transitive CVE, it includes reachability context where we can determine it. The goal is to give developers enough signal to prioritize without requiring them to manually trace the call graph themselves.
What Changes at Scale
The transitive problem compounds as engineering estates grow. A single repository might have manageable transitive exposure. Fifty repositories sharing common internal libraries, each of which brings its own transitive tree, creates a graph that can't be reasoned about manually.
Shared internal libraries are a specific complication. If your internal utils library depends on a transitive that carries a CVE, every repository that depends on utils inherits that exposure. Fixing it requires updating utils and then propagating that update across all downstream consumers, potentially touching dozens of repositories and their CI pipelines. If the governance tooling doesn't understand library fan-out, the fix looks like one PR when it actually requires fifty.
This is why transitive vulnerability management is fundamentally a graph problem. Effective SCA at scale requires understanding the dependency graph across repositories, not just within individual ones. That's the layer where Repohelm operates, and it's also why we built multi-repo awareness into the policy engine from the beginning rather than treating each repository as an isolated unit.
What Teams Can Do Now
Some practical starting points, regardless of what tooling you're using:
First, generate a full resolved dependency graph for your most critical repositories. Don't rely on the direct manifest alone. Use npm list --all, pip-compile with extras, or mvn dependency:tree to see the full picture. Count the transitives. Most teams are surprised by the number.
Second, configure your SCA tooling to alert on transitive CVEs, not just direct ones. Some default configurations only scan the direct layer. Check your scanner's documentation to confirm what depth it's actually analyzing.
Third, track which internal shared libraries are the largest dependency multipliers. The library that 40 services depend on is the highest-leverage point for transitive remediation. A single update to that library eliminates the same CVE from 40 dependency graphs simultaneously.
The lockfile is not the enemy. It's an integrity tool doing its job. The gap is in treating the lockfile as a security artifact rather than a reproducibility artifact. Those are different guarantees, and conflating them leaves real exposure uncovered.