getNextFixVersion panics on an advisory with an unparsable `fixed` version, crashing result output
- Dominant language
- Go
- Stars
- 11k
- Forks
- 792
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 48
Description
### Summary
`internal/output/output_result.go` builds the scan result for every matched
package. For each vulnerability it calls `getNextFixVersion`, which parses the
`fixed` string of every affected range event with `semantic.MustParse`
(`output_result.go:590`). `MustParse` panics when the ecosystem's version parser
returns an error, so a single advisory whose `fixed` value is unparsable aborts
the entire scan **after** matching has already succeeded. Because every output
format (table, JSON, SARIF, HTML) routes through `BuildResults`, all of them
crash.
This is a distinct site from #2867 (match path, ecosystem-name `MustParse`) and
#2837 (match path, version parse). Neither touched the output/result-building
path.
### Affected code
`internal/output/output_result.go:590`:
```go
order, _ = semantic.MustParse(affectedEvent.GetFixed(), ecosystemPrefix).CompareStr(minFixVersion)
```
`semantic.Parse` returns `ErrUnsupportedEcosystem` for ecosystems with no
version parser, and `ErrInvalidVersion` for a malformed version in ecosystems
whose parser validates input (CRAN, Debian, Alpine, Ubuntu, Hackage, ...).
`MustParse` turns either error into a panic. The guard at `:584` does not
prevent this: `vp.CompareStr(GetFixed())` returns `(0, err)` on an unparsable
`fixed`, the error is discarded, so `order == 0`, the `order > 0` skip does not
fire, and execution reaches the panicking `MustParse`.
### Reproduction (public CLI, offline DB)
Reproduces end-to-end through the documented `--offline-vulnerabilities /
--local-db-path` feature — no internal calls. `mypkg@1.0.0` (CRAN) matches an
advisory by its `versions` list, and result building then parses the range's
malformed `fixed` value and panics. Real advisories routinely carry both a
`versions` list and `ranges`.
```bash
WORK="$(mktemp -d)"
mkdir -p "$WORK/target"
cat > "$WORK/target/renv.lock" <<'EOF'
{ "R": { "Version": "4.3.0" },
"Packages": { "mypkg": { "Package": "mypkg", "Version": "1.0.0", "Repository": "CRAN" } } }
EOF
python3 - "$WORK" <<'PY'
import json,os,sys,zipfile
work=sys.argv[1]
adv={"id":"CB-CRAN-REPRO","affected":[
{"package":{"ecosystem":"CRAN","name":"mypkg"},
"versions":["1.0.0"],
"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"notaversion"}]}]}]}
zp=os.path.join(work,"db","osv-scanner","CRAN","all.zip")
os.makedirs(os.path.dirname(zp),exist_ok=True)
zipfile.ZipFile(zp,"w",zipfile.ZIP_DEFLATED).writestr("CB-CRAN-REPRO.json",json.dumps(adv))
PY
go run ./cmd/osv-scanner scan source \
--offline-vulnerabilities --local-db-path "$WORK/db" "$WORK/target"
```
### Actual behavior
```
panic: invalid version: 'notaversion' is not allowed
semantic.MustParse semantic/parse.go:36
output.getNextFixVersion internal/output/output_result.go:590
output.updateVuln internal/output/output_result.go:541
output.processPackage internal/output/output_result.go:462
output.BuildResults internal/output/output_result.go:202
output.PrintTableResults internal/output/table.go:30
```
### Expected behavior
An unparsable `fixed` version is skipped (that event simply cannot be offered as
a fix); the scan completes and reports the matched vulnerability with no
available fix.
### Why it is reachable without an attacker
The per-ecosystem DB zip stores the full advisory record, and the vendored
`osv-schema` / `semantic` parsers are pinned per release while osv.dev's data
evolves. Any advisory that carries a `fixed` string this build cannot parse —
future syntax, an ecosystem whose parser validates input, or a self-maintained
local DB — crashes result building on a package that otherwise matched. Broad
blast radius for a tool that runs in CI.
### Suggested fix
Use the non-panicking `semantic.Parse` and skip the event on error, mirroring
the "skip, don't panic on unsupported ecosystems/versions" approach already
taken in #2837/#2867. The default (parsable) path is unchanged.
I have a fix and reproducing tests ready and would be happy to open a PR once
this is assigned.
---
**Related, separate observation (not part of this fix):** `internal/output/table.go:334`
uses `osvecosystem.MustParse(eco.Name)` on the scanned package's ecosystem in the
table renderer. Reachability depends on whether an unrecognized ecosystem can
reach output (e.g. via SBOM input); worth a separate look but not covered here.
Contributor guide
Research direction
Start in internal/output/output_result.go at getNextFixVersion around line 590, then run the provided offline database reproduction to observe the result-building panic. Check the existing output/result tests and the related issue references for the intended skip-on-parse-error behavior. Done means all output formats complete successfully, report the matched vulnerability, and offer no fix for the unparsable event.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100