osv-scanner is Google's open-source vulnerability scanner. It matches the dependencies in your project against advisories from the OSV database, and in offline mode it runs entirely against a local copy of that database, the mode you reach for in air-gapped environments and reproducible CI builds.
We found that a single OSV advisory carrying an ecosystem name the scanner didn't recognize would panic the matcher and abort the entire scan. Not skip one advisory, not warn: crash the whole run.
One advisory whose ecosystem string isn't in your build's pinned schema, and the scan of every package stops with a panic.
MustParse means must-not-failDeep in the matcher, vulns.IsAffected and vulns.AffectsEcosystem resolved an advisory's ecosystem with osvecosystem.MustParse. As the name says, MustParse panics on any string the vendored osv-schema registry doesn't recognize:
// matcher, before the fix eco := osvecosystem.MustParse(advisory.Ecosystem) // panics on an unknown ecosystem // ...one unrecognized advisory here aborts the whole scan
Here's why that is reachable in normal operation, not a contrived input: the OSV database on osv.dev adds new ecosystems over time, while any given osv-scanner binary is built against a pinned snapshot of the schema. The day the database ships an advisory for an ecosystem newer than your installed scanner knows about, that advisory's ecosystem string is “unknown” to MustParse, and the panic takes the entire offline scan down with it.
The failure mode is the worst kind for a scanner. It doesn't quietly miss one advisory; it aborts the whole run. A CI gate that depends on osv-scanner to fail the build on a known-vulnerable dependency simply stops producing a verdict the moment an unrelated new ecosystem lands upstream, and offline, reproducible builds are precisely where you can't just pull a fresh schema to make the crash go away. A robustness bug in the tool becomes a gap in the thing the tool exists to guarantee.
PR #2882 (maintainer-approved), addressing issue #2867, replaces the panic-prone call with a parseAffectedEcosystem helper built on the non-panicking osvecosystem.Parse. When an ecosystem isn't recognized, the affected entry is skipped with a debug-level log instead of crashing, mirroring the error handling the scanner already used for version parsing.
// after the fix eco, err := osvecosystem.Parse(advisory.Ecosystem) if err != nil { // skip this affected entry, keep scanning log.Debug(...); continue }
The practical effect: older builds now gracefully skip the entries they can't interpret instead of aborting all offline scans when osv.dev moves faster than the pinned schema.
A scanner is a state machine over its inputs, and its inputs include the advisory data it's asked to interpret. Feed it something just outside its modeled domain (an ecosystem that isn't in the pinned enumeration), and the correct behavior is to degrade, not halt. RedMirror looks for exactly that: a reachable input that drives a program into a state it has no graceful path out of. Here it's an advisory field with an unrecognized value reaching a MustParse that treats “unknown” as “impossible.” We surface the input that gets there so it can be handled before it takes a scan down in production.
Parsers, matchers, and anything that consumes external data have states like this: an input the code assumed could never appear. RedMirror finds the reachable one and reports it as a concrete path, so a robustness gap doesn't become a security gap.
Start an audit Read the docs