You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Read OSV upstream, and scan an RPM that was never installed
Two changes. The first repairs a feature that already shipped; the second
is new surface that depends on it.
## OSV's upstream field
OSV relates records two ways. `aliases` means "this is the same
vulnerability under another name" -- bidirectional identity. `upstream`
means "this record addresses these upstream vulnerabilities" --
directional, one-to-many. vexscan read only `aliases`.
Every distro database uses `upstream` and none uses `aliases`:
SUSE-SU-2026:0312-1 aliases=0 upstream=8
RHSA-2024:2447 aliases=0 upstream=7
DEBIAN-CVE-2023-45853 aliases=0 upstream=1
Debian and Ubuntu survived only by accident -- their ids contain the CVE,
so it was recovered by stripping the prefix. SUSE-SU and RHSA ids contain
no CVE anywhere, so on those two distros --triage scored nothing at all
and --cves matched nothing. On SUSE that mattered most: it publishes no
CVSS, so every advisory renders UNKNOWN and EPSS is the only ordering
signal there is. It was the one that did not work.
`aliases()` stays identity-only, so --vexhub is unchanged: a hub statement
about one of eight CVEs must not suppress a finding about all eight. The
new `cveSets()` sits beside it for the consumers that join on CVE and take
a max over the set. buildMap and borrowSeverity are untouched for the same
reason -- filing one SUSE-SU under 8 CVE keys, or copying one CVE's vector
onto the other seven, would both be wrong over a bundle.
A bundle stays one row. Its priority is its worst member, --details lists
what it fixes and names the id that scored, and an unscored bundle says
"none of this advisory's 3 CVEs" rather than blaming one.
## --rpm
`--rootfs` over an unpacked RPM printed "No findings", because ospkg
inventories from an rpm *database* and an RPM *file* is not one.
--rpm takes a file, a directory of them, or a URL. Everything needed is in
the header, ahead of the compressed payload, and each section states its
own length in its first 16 bytes -- so the reader knows where the header
ends and stops there. Over HTTP that is a plain GET with the body closed
early, not a range request, so it works against mirrors that ignore Range.
Rocky's openssl-libs costs 17.5 KB of 2.3 MB. The payload is never
decompressed and there is no xz or zstd dependency: FILECLASS/CLASSDICT
carry file(1)'s classification of every entry in the header.
SOURCERPM is why this finds anything. Red Hat and SUSE file advisories
under the source package, and OSVNames() already queries both -- which on
the SLE package is the difference between 32 findings (openssl-3) and none
(libopenssl3). The distribution comes from VENDOR/DISTRIBUTION, so no
/etc/os-release is needed. A distribution OSV does not carry is an error
naming --osv-ecosystem, not a guess at a near neighbour, because querying
the wrong ecosystem answers with nothing and reads exactly like a clean
package. Two distributions in one directory is the same error.
There is no filesystem, so no closure can run, so nothing is ever linked
and nothing is ever ruled unreachable. What the header can still answer --
does this package install any code at all -- it does, reusing pkgdb-no-code
verbatim because it is the same evidence. Everything else is undetermined,
and the report says so at both ends. The honest measure of what that costs:
on bci-base:15.6 the reachability test ruled out one finding of 47, and it
did it via pkgdb-no-code.
The tree is a real empty directory rather than a nil filesystem, so every
walk and every Unreadable check runs unchanged and gets the truthful
answer. A package file that will not parse is named with its reason and
exits 1, the Unreadable rule applied to a different kind of tree.
rpmsrc is its own package rather than living under target, because pkgdb
imports target and pkgdb promises never to touch the network.
## Also
writePriority counts affected, non-vexed rows, and a metadata-only scan has
none by construction -- so it printed "0 scored" directly above a table of
99.2% EPSS values. It now prints no count over an empty population, and
keeps the feed dates.
## Measured
--rpm libopenssl3 (local) SUSE, 32 findings via the source name
--rpm rocky openssl-libs Rocky Linux:9 from DISTRIBUTION, 17.5 KB of 2.3 MB
--rpm openssl-perl 2 ruled out, pkgdb-no-code
--rpm dir --format inventory 2 packages, the bad file named, exit 1
--rpm libopenssl3 --triage 28 of 32 scored, against 0 before
--image debian:12 byte-identical to v0.5.0, with and without --triage
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
epss: 98.7% percentile (epss 0.47621) for CVE-2025-15467, highest of 8
1140
+
```
1141
+
1142
+
This matters most on SUSE, which publishes **no CVSS at all** — all 46
1143
+
advisories on `bci/bci-base:15.6` render UNKNOWN, so EPSS is the only ordering
1144
+
signal that distro has. Before v0.5.1 it scored **0 of 47** findings there,
1145
+
because SUSE and Red Hat ids name no CVE and carry no aliases; it now scores
1146
+
41, and the six at or above the 90th percentile sort to the top of a table
1147
+
that was previously in no meaningful order.
1001
1148
1002
1149
-**Both feeds are keyed by CVE, and many advisories are not.** On the Rancher
1003
1150
image below, *not one* of 865 findings carries a CVE in any of its own fields —
@@ -1192,6 +1339,7 @@ be read, or part of the tree could not be read, `2` the command line was wrong.
1192
1339
|`--image`|| Container image to inspect |
1193
1340
|`--rootfs`|| Filesystem tree already on disk to inspect — see [`--rootfs`](#scanning-a-filesystem-instead-of-an-image---rootfs)|
1194
1341
|`--repo`|| Git source repo to analyze: govulncheck source mode for Go, lock file inventory for Python and npm |
1342
+
|`--rpm`|| RPM package file to scan without installing it — a path, a directory of them, or a URL; repeatable. Reads only the header, so a URL costs kilobytes not megabytes — see [`--rpm`](#scanning-package-files---rpm)|
1195
1343
|`--package`|| Package to check: purl, `ecosystem:name`, or bare name; repeatable |
1196
1344
|`--cves`|| CVE / GHSA / GO / RHSA / DSA ids; alone, resolved against the whole target |
1197
1345
|`--all`|`false`| Check everything each ecosystem can enumerate |
@@ -1202,7 +1350,7 @@ be read, or part of the tree could not be read, `2` the command line was wrong.
1202
1350
|`--repo-path`|`.`| Subdirectory within `--repo` to scan — the Go module, or the directory holding the lock files |
1203
1351
|`--version`|*(auto)*| Override the module version (image mode) instead of reading build info |
1204
1352
|`--go-version`|*(auto)*| Pin the Go toolchain for `--repo`, e.g. `1.24.0` (useful with `golang:stdlib`) |
1205
-
|`--osv-ecosystem`|*(auto)*| Override the OSV ecosystem derived from os-release, e.g. `Debian:12`|
1353
+
|`--osv-ecosystem`|*(auto)*| Override the OSV ecosystem derived from os-release, or from the `VENDOR`/`DISTRIBUTION` headers under `--rpm`, e.g. `Debian:12`|
1206
1354
|`--roots`|| Extra entrypoints for the closures — shared libraries and language imports; repeatable |
1207
1355
|`--vexhub`|| VEX Repository to check findings against, e.g. `https://github.com/rancher/vexhub` (also a raw base URL or a local directory); repeatable, earliest wins — see [VEX hubs](#vex-hubs---vexhub)|
1208
1356
|`--severity`|*(all)*| Only report findings at these severities: `CRITICAL`, `HIGH`, `UNKNOWN`, `MEDIUM`, `LOW`, `NONE`; comma-separated or repeatable. `UNKNOWN` must be named to be shown — see [Filtering by severity](#filtering-by-severity---severity)|
0 commit comments