This is a story about almost building the wrong thing. The punchline is in the title, but the numbers are worth walking through, because the wrong thing looked completely reasonable until we measured.

The gap

Aguara's advisory matcher consumed exact package@version tuples. An OSV advisory that says "lodahs 1.0.0, 1.0.1, and 1.0.2 are malicious" matched fine. An advisory that describes affected versions as a range, introduced here, fixed there, was dropped at import. The docs said so honestly: "parser ready; range-aware matching deferred."

The headline numbers looked alarming. For Go, 97% of OSV records were ranges-only: thousands of advisories Aguara could parse but never match. Similar story for crates.io. The obvious roadmap wrote itself: build version-comparison support per ecosystem, flip the gates, unlock the coverage.

The plan we almost executed

Version ordering is a different language in every ecosystem, and most of them are hostile to hand-rolling:

  • PyPI uses PEP 440: epochs, pre/post/dev releases, local version segments. Subtly wrong implementations are the norm.
  • Maven uses ComparableVersion, with qualifier ordering where alpha < beta < milestone < rc < "" < sp. The most idiosyncratic of the lot.
  • Packagist normalizes four-part versions with stability suffixes and branch names.
  • RubyGems has Gem::Version, which is not semver, just close enough to fool you.
  • Go adds pseudo-versions and +incompatible on top of semver.

Seven grammars, each a source of subtle false positives in a tool whose whole posture is "a wrong affected verdict is worse than a missed one". We had the phasing planned, lowest-risk grammar first.

Then we measured

Before writing any of it, we reordered the import funnel so one question could be answered precisely: of the ranges-only advisories, how many are actually malicious-package records, the only kind Aguara matches, and what shape are their ranges?

EcosystemRanges-onlyMalicious ∩ ranges-onlyOf which all-versions shape
npm202,880197,286196,191 (99.4%)
PyPI6,7316,3776,373 (99.9%)
Go6,9833318
crates.io2,39551
Maven75520
Packagist62921
NuGet4800
RubyGems1800

Three things fell out of that table, and each one killed part of the plan.

First: Go's "97% unlock" was a mirage. Nearly all of those ranges-only records are regular CVE advisories from govulndb, which Aguara's malicious-only policy drops anyway. The real malicious-and-ranges number for Go is 33 records. For Maven, 2. Grammar work for those ecosystems would have bought almost nothing.

Second: the real gap was enormous, and it was not a grammar problem. 197,000 npm and 6,400 PyPI malicious advisories were being dropped, and over 99% of them share one shape: introduced: 0, no upper bound. Every version of the package is malicious. Which makes sense once you say it out loud: most malicious packages are born malicious. A typosquat has no clean history. A dependency-confusion probe has no fixed release. The compromised-legitimate-package case, the one that actually needs version bounds, is the rare exception, not the rule.

Third: matching the dominant shape needs zero version comparison. If every version is affected, the entire evaluation is "does this package name appear in your lockfile". That works identically for PyPI without touching PEP 440, for Maven without ComparableVersion, for every ecosystem at once.

What shipped instead

Aguara v0.26.0 imports all-versions advisories as a compact set keyed by ecosystem and package name: around 200,000 entries, 2.4 MB compressed, matched by name lookup. The one bounded-range population worth evaluating, about 1,100 npm records, goes through the semver engine that already existed for npm. Total new version-grammar code: zero lines. The per-ecosystem grammar plan is cancelled, on the record, unless OSV's malicious-record shapes change.

One sharp edge appeared during release review, and it is the same lesson from the other direction. Aguara's importer admits some advisories on high-confidence keywords in their text. That channel is acceptable for exact-version records, where a false positive flags a handful of versions. Extended naively to ranges, it leaked generic CVEs for axios, @angular/core, and playwright as "malicious", because their advisory text mentioned things like credential exfiltration. A keyword false positive on a range flags every version below the bound. So the range channels require the firm malicious-package signal, the MAL- namespace or an OpenSSF origin, and keywords stay where the blast radius is small. The measured cost was 28 packages, nearly all redundant with existing coverage.

The lesson

The roadmap said "build seven version grammars". The data said "build a name lookup". The difference between those two projects is months of work and a permanent false-positive surface, and the only thing standing between them was one measurement pass that took a day.

Detection coverage claims deserve the same scrutiny as detection logic. "Supports range matching" would have been true either way; what matters is how many real advisories it turns into real findings, and for that, the distribution of the data decides, not the elegance of the engine. The v0.26.0 release post has the resulting numbers; the short version is that one import-side change multiplied usable malicious-package coverage by ten.

The coverage this unlocked ships in v0.26.0

228,000 known-malicious packages, matched offline, before install. One measurement pass paid for all of it.