Load Path Research The mechanism you have vs the property you need

Default is not a control · Threat model drift

A clean CVE scan is not a healthy dependency tree.

A Clean CVE Scan Is Not a Healthy Dependency Tree

The CVE system names disclosed flaws and its enrichment layer is now rationed, while supply-chain failures include maintainer takeover and malicious sub-dependencies that no identifier describes until after discovery, and abandoned components that no identifier describes at all; where they touch, a dependency programme gated on CVE matches sees only the disclosed and enriched fraction of its exposure, and has no measure at all for maintenance or maintainer risk.

“The dependency scan came back with no critical CVEs, so our third-party code is fine.”

Let me stop you there, before that sentence goes into the release checklist as a sign-off.

A clean CVE scan is not a healthy dependency tree.

A clean scan says one thing.

None of the components it recognised matched a record of a flaw that somebody had already disclosed, that had been assigned an identifier, and that carried enough data for the tool to match and rank it.

That is a real result.

It is also a much smaller result than “our third-party code is fine.”

It says nothing about a maintainer who handed the keys to a stranger last month.

Nothing about a new sub-dependency that arrived in a patch release.

Nothing about the component whose last upstream commit predates your current CI system.

Those aren’t exotic edge cases hiding beyond the scanner’s field of view.

They are the failure modes the dependency tree actually has.

A CVE is an identifier for a disclosed flaw.

The identifier comes after the disclosure.

Which means the scanner is, by construction, an instrument for looking backwards.

1. “But the Scanner Checks Every Package Against the Vulnerability Database.”

Which database?

And checks for what, exactly?

The identifier itself is close to free to produce. It is the enrichment, the CVSS score, the CWE mapping and the CPE applicability data, that lets a tool match a record to your inventory and put it in a severity column.

That layer is now rationed.

The 2025 CVE total was 48,185, up 20.6% on 2024’s 39,962, on a cumulative catalogue past 344,258 records.

NIST enriched nearly 42,000 CVEs in 2025, 45% more than in any prior year.

Then in April 2026 it formally stopped trying to enrich everything. Automatic enrichment now covers three categories: CVEs on CISA’s Known Exploited Vulnerabilities catalogue, flaws in US federal government software, and flaws in “critical software” as US Executive Order 14028 defines it.

Everything else waits for capacity.

A record without enrichment cannot be auto-prioritised and cannot be matched against a CPE inventory. It falls off the severity-ranked dashboard while the flaw stays public.

So “no critical CVEs” can mean no critical flaws.

It can also mean flaws nobody has scored.

Your dashboard shows both as the same empty cell.

Nor is NVD the only record, or always the first one.

GitHub became a CNA in September 2020. A maintainer publishing a GitHub Security Advisory can request a CVE ID through GitHub, producing one flaw with two identifiers. The GitHub Advisory Database merges those GHSAs with records imported from NVD and MITRE’s CVE list, scoped to open-source packages.

The maintainer can publish the moment they are ready, whether or not NVD ever enriches the record.

That is why GHSA has become a de facto primary source for open-source tooling, not a convenience mirror.

A scanner fed only from NVD is reading the slower copy.

And “the vulnerability database” is not one database anywhere.

The EU’s European Vulnerability Database, run by ENISA and operational since 13 May 2025, aggregates CVE and other public sources under the NIS2 Directive. It is a layer on top of CVE, not a rival namespace.

China runs two state databases with their own identifiers: CNNVD under the Ministry of State Security, CNVD under CNCERT. Comparisons of publication dates found 0.55% of CNNVD entries and 0.18% of CNVD entries, around 1,400 cases combined, published before the corresponding CVE or NVD record, some by months.

Small percentages.

Also a plain demonstration that when a flaw becomes public and when your scanner’s feed learns about it are two different dates.

Whether a state-security-affiliated database should publish first is a live dispute about disclosure norms. That it sometimes does is not.

Your scan inherits the timing of whichever feed you chose, and the gaps of every feed you didn’t.

2. “But If Something Were Wrong With a Package, It Would Have a CVE.”

Eventually.

If somebody found it.

If somebody reported it.

If somebody requested an identifier.

Take xz-utils.

A persona calling itself “Jia Tan” began contributing to the compression library around 2021, in a campaign that ran for more than two years.

Alongside it, accounts including “Jigar Kumar”, “krygorin4545” and “misoeater91”, suspected pseudonyms with no visible history outside the campaign, pressured the lone maintainer, Lasse Collin, in public channels. The project was under-maintained, they said. It needed more maintainers.

It got one.

This is the structure worth staring at. In a dependency chain the maintainer is the one party who actually reads and writes the code. The registry, the build system, your scanner and your release checklist all trust the maintainer. None of them independently verifies the code.

Compromise or wear down that one person and everything downstream inherits the result, correctly published and correctly versioned.

That persona won maintainer permissions, and used them to put a backdoor into version 5.6.0 in February 2024.

Andres Freund discovered it on 29 March 2024.

It is tracked as CVE-2024-3094.

After discovery.

Every scan of a system carrying 5.6.0 between February and the end of March came back clean against that backdoor, because the record that would have matched it did not exist yet.

The scanner was not broken.

It was answering the question it was built for.

No weakness in the code gave the campaign its way in. It won through a question no CVE record describes: who decides who gets commit access to unpaid infrastructure?

Eyes on what?

The package you chose is not the only code you run.

In 2018 Dominic Tarr, the original maintainer of the npm package event-stream, handed full publish rights to a contributor going by “right9ctrl”, a stranger who offered to take over a project Tarr no longer had time for.

On 9 September 2018 that account added flatmap-stream as a dependency of event-stream 3.3.6.

The payload was encrypted and aimed at cryptocurrency in one specific downstream Bitcoin-wallet application.

The malicious code was not in the headline package.

It was in a sub-dependency the headline package pulled in.

A user flagged it as suspicious on 20 November 2018. From 9 September to 20 November is 72 days, roughly two and a half months unreported.

Your popular-package policy approved event-stream.

It never looked at flatmap-stream.

And the xz case cuts the other way: xz-utils was infrastructure much of the internet quietly depends on, maintained for most of its life by one unpaid person.

Popularity measures how many people depend on something.

It doesn’t measure how many people maintain it.

Those are different numbers, and an attacker only needs the second one to be small.

4. “But the xz Backdoor Was Caught Before It Reached a Stable Release.”

It was.

Credit where due: at discovery, the backdoored versions had reached Fedora Rawhide, the Fedora 40 beta and Debian unstable, and had not propagated to any stable release.

That makes it a near-miss.

Not a contained incident.

Now look at what caught it.

One engineer, who found something wrong.

Not a scanner.

Not a CVE feed.

Not a severity dashboard.

The defence that worked sat entirely outside the CVE-keyed pipeline, and that pipeline had nothing to say until after it had worked.

A near-miss is evidence that a particular person noticed in time.

It is not evidence that your dependency programme would have.

If your programme’s only sensor is a record lookup, it is waiting for somebody else’s near-miss to become a record.

5. “But Updating Stale Components Is Churn With No Finding Behind It.”

That depends entirely on what you count as a finding.

The 2026 Open Source Security and Risk Analysis report, an audit of codebases scanned during due-diligence and security engagements, found open source in 98% of codebases.

87% carried at least one known vulnerability.

The mean was 581 vulnerabilities per codebase, against 280 the year before. That is 581 ÷ 280 ≈ 2.1, a little over double in one year.

78% carried at least one high-risk vulnerability.

44% carried at least one critical.

Now the maintenance side, which is worse.

92% had components four or more years out of date.

93% had components with no upstream development activity in two or more years.

93% were not on the latest version of at least one component.

The report’s own summary: every maintenance metric now exceeds 90%.

A component with no upstream activity for two years produces no CVE for its staleness.

Nobody is fixing it.

Often nobody is reading it.

If a flaw is found, there may be nobody to ship the patch the CVE record will eventually point at.

That is a finding.

It just isn’t one a CVE match can generate.

A programme gated only on CVE matches has no measure at all for this, which is how nine in ten codebases get to carry it without anyone writing it down.

6. “But the Registries Remove Malicious Packages.”

After someone reports them.

npm hosts over 3.1 million packages. PyPI hosts 872,191 projects.

No registry reviews the content of publishes at that scale.

What a registry does verify is narrower: that the account publishing a package name is the account that has always published it, and, increasingly, that it cleared a second factor.

npm requires two-factor authentication or a scoped, non-bypassable access token to publish or change package settings.

PyPI required 2FA for every account maintaining a project or organisation by the end of 2023.

Neither check inspects what the code does.

And in event-stream, identity checks would have passed. The publisher was the legitimate holder of publish rights. Tarr had given them away.

npm’s moderation relies on user reports to take down packages that are low quality, insecure or malicious.

In event-stream, npm removed flatmap-stream and the tainted release six days after the report, and took over ownership of the package.

Operationally sound.

Also, by definition, after the fact.

A takedown is a registry’s response to discovery.

The registries are not negligent for this. Sonatype’s own framing is that “registry infrastructure is now critical plumbing, and the cost of operating the commons rises faster than most stakeholders realize.” There is no per-download fee to fund human review of every publish, which is why registry security arrives as automatable checks: 2FA mandates, scoped tokens, provenance.

Useful checks.

About identity.

A takedown is still not a control you own, and it does nothing for the builds you shipped in the 78 days between publication and removal: 72 unreported, then six more to the takedown.

7. No, This Does Not Mean CVE Scanning Is Useless.

It isn’t.

The CVE match is necessary triage. With a mean of 581 known vulnerabilities per codebase, manual review does not scale, and automated matching against disclosed flaws is the only way to work that volume at all.

Keep it.

Feed it better.

This also does not mean every stale component is compromised.

Or that every single-maintainer project is a Jia Tan waiting to happen.

Most abandoned code is simply abandoned.

Most lone maintainers are simply overworked.

Treating every one as a live threat would make the list useless in a different way.

The correction is narrower.

A clean match speaks for flaws that were disclosed and enriched.

Maintenance risk and maintainer risk are real, measurable conditions of a dependency tree.

They need measures of their own, because the CVE system was never designed to supply them.

8. What You Should Actually Do

Widen the feed. Pull advisories from GHSA and OSV as well as NVD. Much of this data is free and community-run: RustSec feeds OSV, the GitHub Advisory Database and Debian’s security tracker; npm has shipped npm audit since npm v6. Commercial tools compete on coverage and workflow, not exclusive access.

Give unenriched CVEs their own queue. A record without CVSS or CPE data is not low severity. It is unscored. Track it as such rather than letting it drop off the ranked dashboard.

Record maintenance beside vulnerabilities. For each critical dependency: last upstream release, number of active maintainers, and vulnerability count, side by side.

Flag abandonment. Any component with no upstream activity in two or more years gets a named decision: replace it, fork it, or take ownership of it.

Watch the keys. A change in maintainers or publish rights on a critical dependency is an event worth a review, not a line in a changelog.

Review new transitive dependencies before accepting them. event-stream’s problem arrived as a new sub-dependency in a patch release. That is exactly the moment to look.

9. The Actual Model Is Not Complicated.

A CVE match answers: does this component carry a flaw that has been disclosed, identified and, for ranking, enriched?

Maintenance data answers: is anybody still working on this component?

Maintainer data answers: who can publish it, and did that change?

Dependency review answers: what code did this update just bring with it?

A clean scan answers the first question.

The xz backdoor and flatmap-stream were both, for weeks or months, the second, third and fourth.

A dependency with no matching CVE has not been cleared.

It has been unassessed.

Run the scan.

Then measure the rest of the tree.

Objections

  1. Since April 2026 NIST auto-enriches only CVEs on CISA's Known Exploited Vulnerabilities catalog, in federal government software, or in EO 14028 critical software; unenriched records cannot be auto-prioritized or matched against CPE inventories.
  2. GitHub became a CNA in September 2020, and because a maintainer can publish a GitHub Security Advisory as soon as they are ready, independent of NVD enrichment, GHSA has become a de facto primary source for open-source tooling.
  3. The 2026 OSSRA audit found open source in 98% of codebases, at least one known vulnerability in 87%, a mean of 581 vulnerabilities per codebase, and components with no upstream development in two or more years in 93%.
  4. The xz-utils backdoor came from a persona who, in a campaign of more than two years, won maintainer permissions through contributions and sockpuppet pressure on a lone maintainer and then planted the backdoor in version 5.6.0; it was found on 29 March 2024 in development branches, before any stable release.
  5. In 2018 a stranger handed event-stream's publish rights added a malicious sub-dependency, flatmap-stream, which went unreported for about two and a half months; npm's lever was takedown after the fact.

What this does not say

What this does not mean is that CVE-based scanning is useless or that every stale or single-maintainer component is compromised; the CVE match is necessary triage. It means a clean match speaks only for flaws that were disclosed and enriched, and maintenance and maintainer risk need measures of their own.

Raised in review

Why is a clean CVE scan not proof that dependencies are healthy?
A CVE-keyed scanner matches installed components against records of flaws that have already been disclosed, assigned an identifier and, for automated ranking, enriched with CVSS, CWE and CPE data. A maintainer takeover or a malicious sub-dependency has no record until someone discovers it, and an abandoned component has no record at all. A clean match covers the disclosed fraction only.
What does NVD enrichment add to a CVE, and why does it matter for scanning?
Enrichment adds CVSS severity, a CWE weakness mapping and CPE applicability data to a published CVE Record. Without it, a record cannot be auto-prioritized or matched against a CPE-based inventory, so it tends to drop off severity-ranked dashboards even when the flaw is public. Since April 2026, NIST auto-enriches only CVEs in CISA's KEV catalog, federal government software or US Executive Order 14028 critical software.
Is GHSA a replacement for CVE?
No. GitHub has been a CNA since September 2020, so a maintainer publishing a GitHub Security Advisory can also request a CVE ID, giving one vulnerability two identifiers. The GitHub Advisory Database combines those advisories with records imported from NVD and MITRE's CVE list for open-source packages. Its advantage is timing: a maintainer can publish the moment they are ready, independent of NVD enrichment.
Which free advisory sources should a dependency programme read besides NVD?
The GitHub Advisory Database and the cross-ecosystem Open Source Vulnerabilities (OSV) database are the obvious additions for open-source packages. The Rust ecosystem's community-run RustSec database feeds OSV, the GitHub Advisory Database and Debian's security tracker, and cargo-audit reads it. npm has shipped a built-in npm audit command since npm v6. Commercial tools compete on coverage and workflow, not exclusive data.
How common are abandoned components in real codebases?
Very. The 2026 Open Source Security and Risk Analysis report, an audit of codebases scanned in due-diligence and security engagements, found components with no upstream development activity in two or more years in 93% of codebases, components four or more years out of date in 92%, and codebases not on the latest version of at least one component in 93%. Its own summary: every maintenance metric now exceeds 90%.
How many known vulnerabilities does a typical codebase carry?
The 2026 OSSRA report found open source in 98% of audited codebases, at least one known vulnerability in 87%, at least one high-risk vulnerability in 78% and at least one critical vulnerability in 44%. The mean was 581 vulnerabilities per codebase, up from 280 the year before, which is a little over double. These are single-year figures and move sharply from one report to the next.
What happened in the xz-utils backdoor?
A persona using the name Jia Tan contributed to xz-utils from around 2021 in a campaign of more than two years, while suspected sockpuppet accounts pressured its lone maintainer, Lasse Collin, to add maintainers. The persona won maintainer permissions and introduced a backdoor into version 5.6.0 in February 2024. Andres Freund discovered it on 29 March 2024, and it was tracked afterwards as CVE-2024-3094.
Did the xz backdoor reach stable Linux releases?
No. At discovery on 29 March 2024, the backdoored versions had reached development branches of major distributions, Fedora Rawhide and the Fedora 40 beta and Debian unstable, and had not propagated to any stable release. That makes it a near-miss rather than a completed compromise. It was caught by one engineer who noticed something wrong, not by a scanner matching a record.
What was the event-stream incident?
In 2018 Dominic Tarr, the original maintainer of the npm package event-stream, handed full publish rights to a stranger who offered to maintain it. On 9 September 2018 that account added flatmap-stream as a dependency of the 3.3.6 release, carrying an encrypted payload aimed at cryptocurrency in one downstream Bitcoin-wallet application. A user flagged it on 20 November 2018, and npm removed both six days later.
Do npm and PyPI review packages before publication?
No. npm hosts over 3.1 million packages and PyPI 872,191 projects, far more than any registry could review. Registries verify who publishes: npm requires two-factor authentication or a scoped access token to publish, and PyPI required 2FA for every project maintainer by the end of 2023. Neither check inspects what the code does, and npm relies on user reports to take malicious packages down.
Does a stale or single-maintainer dependency mean it is compromised?
No. Staleness and a lone maintainer are risk conditions, not findings. Most such components are simply unmaintained or quietly maintained by one careful person. The point is that neither condition appears in a CVE match, so a programme that measures only CVE matches cannot see them. Track them as their own measures and decide per critical dependency whether to replace it, fork it or take ownership.
What should a dependency programme record besides vulnerability counts?
For each critical dependency: its last upstream release, its number of active maintainers, any recent change in maintainers or publish rights, and any new transitive dependencies it pulled in. Pull advisories from GHSA and OSV as well as NVD, and keep unenriched CVEs in their own queue so they do not vanish from severity dashboards. Flag components with no upstream activity in two or more years.