Mixed versioned ecosystems duplicate packages in formatted output
- Dominant language
- Go
- Stars
- 11k
- Forks
- 792
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 48
Description
## Summary
`output.BuildResults` duplicates and misattributes packages when one source contains ecosystem names where one is a prefix of another, such as `Alpine` and `Alpine:v3.18`.
This affects the table, vertical, Markdown, and HTML formatters, all of which consume `BuildResults`. The raw JSON formatter is unaffected.
Reproduced on current `main` at `1f87b5cb9781a03a9b8b50dfc1eac1b2056c79d5`.
## Minimal reproduction
Using one `PackageSource` with these two packages:
```go
Packages: []models.PackageVulns{
packageWithVuln("base-package", "Alpine", "OSV-BASE"),
packageWithVuln("versioned-package", "Alpine:v3.18", "OSV-VERSIONED"),
}
```
and printing the ecosystem/package pairs returned by `output.BuildResults` produces:
```text
Alpine -> base-package
Alpine -> versioned-package
Alpine:v3.18 -> versioned-package
```
The versioned package and its vulnerability count appear in both ecosystem sections. The expected result is:
```text
Alpine -> base-package
Alpine:v3.18 -> versioned-package
```
## Root cause
`processSource` supports multiple ecosystems in one source and stores packages under a composite string key:
```go
key := vulnPkg.Package.Ecosystem + ":" + vulnPkg.Package.Name + ":" + vulnPkg.Package.Version
```
It later assigns packages to ecosystem sections using the ecosystem name as a raw prefix:
```go
if !strings.HasPrefix(key, ecosystem) {
continue
}
```
For the `Alpine` section, both `Alpine:base-package:...` and `Alpine:v3.18:versioned-package:...` match.
Mixed base and versioned OS ecosystems are valid inputs, for example when an SBOM contains some OS package PURLs with distro metadata and others without it.
## Suggested fix
Use a structured package key containing separate ecosystem, name, and version fields, then compare the ecosystem field for exact equality. This also avoids relying on delimiters inside package identities.
Add a focused `BuildResults` regression test with `Alpine` and `Alpine:v3.18` packages and assert that each package appears only in its exact ecosystem section.
I searched existing issues and pull requests and did not find this behavior reported. If this approach looks appropriate, please assign the issue to `@Muszic` and I can submit the fix with regression coverage.
Contributor guide
Assessment
This issue has not been assessed yet.