google / google/osv-scanner

getNextFixVersion panics on an advisory with an unparsable `fixed` version, crashing result output

Open Beginner friendly
#2,936 4 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.