Repository navigation
fix(alpine): false positives for apk packages without an origin (o:) field #11322
Description
Description
The Alpine detector reports already fixed vulnerabilities for apk packages that have no o: (origin) field in /lib/apk/db/installed.
Such packages show up in images whose packages are not built with abuild, e.g. Docker Hardened Images.
Original report: #11311.
Reproduction
trivy image --pkg-types os dhi.io/git:2-alpine@sha256:c22bf201b598dabf2e84defb4fdddfb485cc91c215db92614b879b624795b8f2┌─────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │
├─────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┤
│ git-lfs │ CVE-2021-38561 │ HIGH │ fixed │ 3.8.0-r3 │ 3.1.2-r3 │
│ │ CVE-2022-27191 │ HIGH │ fixed │ 3.8.0-r3 │ 3.1.2-r4 │
│ │ CVE-2025-26625 │ HIGH │ fixed │ 3.8.0-r3 │ 3.7.1-r0 │
└─────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┘
The installed version 3.8.0-r3 is higher than every fixed version, yet all three CVEs are reported.
The git-lfs entry in /lib/apk/db/installed (R:, Z: and similar fields omitted):
P:git-lfs
V:3.8.0-r3
A:x86_64
L:MIT
m:Docker Hardened Images <dhi@docker.com>
D:git
The other 19 packages in the image have an o: field; git-lfs does not.
Root cause
- The apk analyzer sets
SrcNameandSrcVersiononly when it parses ano:line (apk.go#L88-L91).
Withouto:both fields stay empty. - The Alpine detector falls back to
NamewhenSrcNameis empty (alpine.go#L89-L92, fix: In alpine use Name to fetch advisories #3079), so advisories for the package are found.
There is no such fallback for the version: it comparesutils.FormatSrcVersion(pkg), which is an empty string (alpine.go#L101). go-apk-versionparses""without an error and treats it as lower than any other version, so every advisory with a fixed version matches.- The report shows the binary version (
FormatVersion), which is why the installed version in the table looks higher than the fixed one.
Similar problems
The same situation — empty source fields when there is no information about the source package — exists elsewhere:
- rpm analyzer: when
SOURCERPMis(none), empty or unparsable,SrcName/SrcVersion/SrcReleasestay empty, butSrcEpochis still taken from the binary package (rpm.go#L131-L139, rpm.go#L179).
With a non-zero epoch,FormatSrcVersionreturns a string like1:instead of an empty one. - Azure Linux: advisories are looked up by
pkg.SrcNamewith no fallback toName(azure.go#L51), so no vulnerabilities are found for such an rpm package (false negative). - Root.io and Seal: the name falls back to
Name, but the version comes fromFormatSrcVersion(rootio.go#L85, seal.go#L110).
checkConstraintsreturnsfalsefor an empty version, so fixed vulnerabilities are missed (false negative); for rpm with a non-zero epoch a string like1:is compared. - RapidFort: already uses
cmp.Or(utils.FormatSrcVersion(pkg), utils.FormatVersion(pkg))(rapidfort.go#L145), but that does not kick in for rpm packages with a non-zero epoch and noSOURCERPM, because of the same1:.
Not affected: the dpkg analyzer fills the source fields from the binary package, the same way dpkg itself does (dpkg.go#L310-L320), so Debian, Ubuntu and Echo always have a source version.
Detectors that compare the binary version (Red Hat, Amazon, Oracle, SUSE, Rocky, Alma, Photon, Wolfi, Chainguard, MinimOS) do not depend on the source version.
Challenges
- The source fields are not only detector input.
They end up in CycloneDX properties and SPDXsourceInfo(encode.go#L409, encode.go#L476, marshal.go#L357) and in RPC.
For dpkg, using the binary package as the source is a rule of the format itself; for apk and rpm it is less clear-cut.
apk has no separate source version at all (subpackages share the main package's version, and the analyzer already takesSrcVersionfromV:), andapk listandapk search --originprint the package name when the origin is empty (app_list.c, app_search.c); the rest of apk-tools (replaces,index --prune-origin,apk query) treats an empty origin as absent rather than equal to the name.
rpmdb has no source package information at all for packages withoutSOURCERPM. utils.FormatSrcVersionis used both by detectors and by the SBOM encoder.- Name and version must be taken consistently: a source name with a binary version (or vice versa) produces a wrong match.
- Checking whether the result of
FormatSrcVersionis empty does not work for rpm because ofSrcEpoch(see above). - Any change to an analyzer's output requires bumping its
Version(), otherwise cached results keep serving the old data.
Before implementation
Please discuss and agree on the approach in this issue before opening a PR.
Activity
aron-intframe commentedon Sep 28, 2026
Reproduced without DHI access: stock
alpine:3.22+apk add git-lfs, then delete the singleo:git-lfsline from/lib/apk/db/installed. Nothing else differs.Two measurements for the approach.
1. Origin-less apk packages already exist in official images, as a second class. Of 20 Alpine-based images I dumped, 5 carry one (python, redis, postgres, rabbitmq, memcached
:alpine). They areapk add --virtualmeta packages:Trivy emits it today with
SrcName: nulland a PURL, so a version fallback hands that timestamp to the comparator. Inert only because the.-prefixed name matches no advisory.2. For apk, a version fallback is a no-op whenever
o:is present. The analyzer readsSrcVersionfrom the sameV:line, so on that dbFormatSrcVersion==FormatVersion 29/29, SrcName!=Name 16/29. Only the name can diverge, never the version.On root cause 3:
NewVersion("")giveserr=<nil>,Valid("")=true, but"" < 0isfalse, so""equals0rather than sorting below everything.Not checked: DHI images (auth), arm64.
Question: fallback in
alpine.goonly, or inapk.gowithanalyzerVersionraised, and should.-prefixed virtual packages be excluded either way?akshar27 commentedon Sep 28, 2026
Would like to take this on, scoped narrowly to the reported apk case — proposing the approach here first, per the note above.
Proposed fix (apk/Alpine only)
In
alpine.go'sDetect,srcNamealready falls back frompkg.SrcNametopkg.Namewhen there's noo:line. GivesourceVersionthe matching fallback, fromutils.FormatSrcVersion(pkg)toutils.FormatVersion(pkg):This is the same
cmp.Orpattern RapidFort already uses (rapidfort.go#L144-145), applied to the one place apk still lacks it.Why this is safe specifically for apk, where the general challenges you listed don't apply:
Challengessection notescmp.Ordoesn't fully solve rpm because a non-zeroSrcEpochmakesFormatSrcVersionreturn a non-empty"1:"even with noSOURCERPM. The apk analyzer (apk.go) never setsSrcEpochorSrcRelease— onlySrcName/SrcVersion, and only together, only from ano:line. So for apk,FormatSrcVersion(pkg)is empty iffSrcNameis empty, which is exactly the conditionsrcName's own fallback already keys on. Name and version end up consistently binary-or-source together, never mixed.apk.go.SrcName/SrcVersionstay exactly as parsed (still empty withouto:); this repo's SBOM output (CycloneDX/SPDX) and the RPC layer are untouched. So this doesn't hit theVersion()cache-bump concern raised in the issue — that only applies to analyzer output changes, and this changes none.Name,SrcName, or advisory lookup (srcNameselection is already correct per the issue's root-cause step 2 and fix: In alpine use Name to fetch advisories #3079) — only the version string the resolved advisories get compared against for packages already routed to the fallback name.Explicitly out of scope for this PR
Per "Similar problems", the RPM/Azure Linux/Root.io/Seal cases are real but structurally different (RPM's epoch makes the
cmp.Or-is-empty check unreliable; Azure Linux has no name fallback at all — false negative, not false positive; Root.io/Seal use fallback name with aFormatSrcVersion-derivedcheckConstraintsthat reads asfalseon empty rather than passing wrong-but-nonempty). Each seems to want its own fix shaped around what actually breaks for it, not one shared helper — happy to look at those separately once this one lands, but proposing to keep this PR to the exact reported case only.Testing
Planning to add a case to
alpine_test.goreproducing this issue's exact repro (git-lfs,3.8.0-r3installed, noo:line, CVE with a lower fixed version) asserting it's no longer reported, alongside the existingo:-present cases to confirm no regression there.Let me know if this scope/approach looks right before I open a PR.
DmitriyLewen commentedon Sep 29, 2026
I suggest discuss about vurtual packages separately.
Hello @akshar27
Thanks for taking on this work. The plan looks good to me.
I've assigned this issue to you.