Add Root IO vulnerability qualifier support#3137
Add Root IO vulnerability qualifier support#3137chait-slim wants to merge 8 commits intoanchore:mainfrom
Conversation
Adds support for Root IO package qualification to enable the NAK (Negative Acknowledgment) pattern, where vulnerabilities with Root IO qualifier only match Root IO patched packages. Implementation: - Add RootIO field to PackageQualifiers in grype/db/v6/blobs.go - Increment DB schema Addition to 6.1.4 with changelog - Add rootio qualifier implementation in grype/pkg/qualifier/rootio/ - Wire up qualifier in vulnerability.go toPackageQualifiers() The Root IO qualifier ensures that Root IO vulnerabilities (from api.root.io/external/osv) only match packages with rootio- prefix, preventing false matches against standard upstream packages. Signed-off-by: Chai Tadmor <chai.tadmor@root.io>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Bumps MatchesSchemaVersion to 1.1.4 to reflect the addition of the
RootIO field in PackageQualifiers (an ADDITION — backward-compatible
optional field).
Signed-off-by: Chai Tadmor <chai.tadmor@root.io>
441f80e to
2b96fcb
Compare
| for _, rel := range relatedEntries.Related { | ||
| aph, ok := rel.(db.AffectedPackageHandle) | ||
| require.True(t, ok, "related entry should be AffectedPackageHandle") | ||
| require.NotNil(t, aph.Package, "Package should not be nil") |
There was a problem hiding this comment.
Can you make these assertions specific? "not empty" type tests are easy to pass with an incorrect value.
There was a problem hiding this comment.
Also, I expected that rootio data would make an unaffected package handle, not an affected package handle.
|
Hi @chait-slim I spent some time testing this out this week. I think it's missing changes to actual matching behavior. In other words, this change puts new rootio records in the database, but nothing queries for them. I think probably what we want to:
We will probably need to do something similar in the APK matcher and other supported rootio ecosystems. |
… model
Rootio OSV records were stored in the DB as AffectedPackageHandle with a
rootio qualifier, but nothing in the matching pipeline queried them — making
the integration a no-op at scan time. Two additional issues: the data model
was semantically wrong (rootio records are NAKs, not disclosures), and the
ecosystem prefix conventions were incorrect.
- Emit UnaffectedPackageHandle for rootio records in the transformer;
rootio data represents backported fixes, not new vulnerability disclosures
- Add two-pass distro matching for rootio OS packages (dpkg, apk): strip
the rootio- prefix to search upstream Debian/Alpine/Ubuntu vulns, then
query rootio unaffected records by the original prefixed name and subtract
any fixes — applied to both direct and upstream packages
- Add bare-name search for rootio language packages (NPM, PyPI, Java) in
the language matcher so upstream CVE records stored without the rootio
prefix are also found
- Fix PyPI prefix: rootio_ (underscore) per PEP 503, not rootio- (hyphen)
- Add Java/Maven detection: io.root. groupId prefix
- Fix NPM StripPrefix to reconstruct scoped packages correctly
(@rootio/babel__core → @babel/core)
- Replace weak NotEmpty assertions with specific per-fixture checks:
package name, CVE alias, fix version constraint, RootIO qualifier, >= range
Signed-off-by: Chai Tadmor <chai.tadmor@root.io>
51a2baf to
72d9a4e
Compare
willmurphyscode
left a comment
There was a problem hiding this comment.
Thanks for putting some more work into this! It's much closer. I have quite a few inline comments, but we're moving in the right direction.
I do think that anchore/vulnerability-match-labels#167 could use some more images and a lot more labels. I left instructions at https://github.com/anchore/vunnel/pull/963/changes#r3065955122 about why some matches aren't showing up in the diff to be labeled.
| @@ -34,34 +35,59 @@ func (m *Matcher) Type() match.MatcherType { | |||
| } | |||
|
|
|||
| func (m *Matcher) Match(store vulnerability.Provider, p pkg.Package) ([]match.Match, []match.IgnoreFilter, error) { | |||
| if rootio.IsRootIOPackage(p) { | |||
There was a problem hiding this comment.
We don't have adequate test coverage for this change.
- Can we get an Alpine-based test image and labels added to Add Root IO provider labels for ubuntu test image vulnerability-match-labels#167 ?
- This needs a to be well unit tested.
| @@ -37,6 +38,10 @@ func (m *Matcher) Type() match.MatcherType { | |||
| } | |||
|
|
|||
| func (m *Matcher) Match(store vulnerability.Provider, p pkg.Package) ([]match.Match, []match.IgnoreFilter, error) { | |||
| if rootio.IsRootIOPackage(p) { | |||
There was a problem hiding this comment.
Changes to matchers need good unit tests.
| return nil, nil, fmt.Errorf("matcher failed to fetch rootio naks distro=%q pkg=%q: %w", p.Distro, p.Name, err) | ||
| } | ||
|
|
||
| remaining := disclosures.Remove(naks) |
There was a problem hiding this comment.
This depends on aliases, but in my testing the aliases seem to be missing from the rootio unaffected package handles, so nothing gets removed. For example, in the ubuntu 22 test images from the vuln match labels PR has rootio-libgcrypt20 at 1.9.4-3ubuntu3.root.io.2 in it, which is listed as fixed at https://osv.dev/vulnerability/ROOT-OS-UBUNTU-2204-CVE-2024-2236, but grype built from this branch finds CVE-2024-2236 on that package anyway.
| return "" | ||
| } | ||
|
|
||
| ecosystemLower := strings.ToLower(ecosystem) |
There was a problem hiding this comment.
I think I'd prefer to use a narrower change here. There are a lot of changes in this file that affect all OSV providers, but are implemented for RootIO. I think it makes sense to instead try to have something like:
if isRootIO {
return handleRootIO(...)
}pretty early in the OSV transformers. This helps everyone out: other OSV providers' bugs can't break RootIO (and vice versa) and reviewers don't have to spend much focus on whether changes to shared parts of the transformer will break other providers.
| // Check if this is an advisory record | ||
| if isAdvisory { | ||
| // For advisory records, emit unaffected packages | ||
| if isAdvisory || isRootIO { |
There was a problem hiding this comment.
https://github.com/anchore/vunnel/pull/963/changes#diff-e3183aa5564d1be5757075e9114b9ca21572612f5cb34afdb6a08f0091dae127R132 should be marking this as advisory type records. I think maybe your test data in this repo is missing that normalization.
| aliases := vulnerability.Aliases | ||
|
|
||
| if isAdvisory { | ||
| aliases = append(aliases, vulnerability.Related...) |
There was a problem hiding this comment.
I think the origin of the bug causing FPs (see https://github.com/anchore/grype/pull/3137/changes#r3065870827 ) is that the Upstream and Related vulnerabilities on your OSV data aren't being added to the aliases here, so the resultSet.Remove call is skipping them (Because ROOT-OS-UBUNTU-2204-CVE-2024-2236 is not the same as CVE-2024-2236 and there's no entry added to the alias table).
The fix for this should probably be basically to around line 36 say, "this is a rootio advisory and we are going to go transform it in handleRootIO(...)". I think that will make this change safer and easier to reason about.
| @@ -17,7 +18,20 @@ func MatchPackageByLanguage(store vulnerability.Provider, p pkg.Package, matcher | |||
| var matches []match.Match | |||
| var ignored []match.IgnoreFilter | |||
|
|
|||
| for _, name := range store.PackageSearchNames(p) { | |||
| searchNames := store.PackageSearchNames(p) | |||
There was a problem hiding this comment.
Changes to matchers need good unit tests.
| @@ -0,0 +1,52 @@ | |||
| { | |||
| "schema_version": "1.6.1", | |||
| "id": "ROOT-APP-NPM-CVE-2022-25883", | |||
There was a problem hiding this comment.
It would be wonderful to get npm and pypi test cases added to anchore/vulnerability-match-labels#167 and we need npm and pypi records at least handled by unit tests in this repo.
…cher unit tests
The core false-positive bug was a two-part alias gap:
1. transform.go: `Related` CVE IDs were only appended to the
VulnerabilityHandle aliases for advisory records, not RootIO records.
Changed `if isAdvisory` → `if isAdvisory || isRootIO` so upstream
CVE IDs (e.g. CVE-2024-2236) are included in aliases for rootio
vulnerability handles.
2. getUnaffectedPackages was passing vuln.Aliases (the original field)
to getUnaffectedBlob, not the augmented alias list that includes
Related IDs. NAK PackageBlob.CVEs were therefore missing the upstream
CVE, causing disclosures.Remove(naks) to find no identity overlap and
skip suppression. Fixed by threading the augmented aliases through
getUnaffectedPackages/getUnaffectedBlob.
Also update test fixtures to include the vunnel advisory-type
normalization (`database_specific.anchore.record_type: advisory`) that
real pipeline data carries, so both isAdvisory and isRootIO are true in
production conditions.
Unit tests added:
- TestRootIORelatedAliases: asserts Related CVE IDs appear in both
VulnerabilityHandle.BlobValue.Aliases and
UnaffectedPackageHandle.BlobValue.CVEs
- TestMatcherApk_RootIOPackage: below-fix, at-fix, above-fix cases
- TestMatcherDpkg_RootIOPackage: below-fix and at-fix cases
- TestMatchPackageByLanguage_RootIONPM: stripped-name search finds
upstream CVEs; NAK produces ignore filters covering the upstream CVE
- TestMatchPackageByLanguage_RootIOPyPI: stripped-name search via
rootio_requests → requests; PEP 440 local version >= upstream fix
Signed-off-by: Chai Tadmor <chai.tadmor@root.io>
Python packages use MatchPackageByEcosystemAndCPEs (not MatchPackageByLanguage), which lacked the rootio prefix-stripping logic. Without it, grype searched for "rootio-jinja2" instead of "Jinja2", finding no CVE records and thus never triggering the NAK suppression. Also accept "rootio-" (hyphen) as a Python package prefix in qualifier.go, since PEP 426 normalizes "rootio_jinja2" to "rootio-jinja2" on install. Signed-off-by: Chai Tadmor <chai.tadmor@root.io>
|
Adds support for Root IO package qualification to enable the NAK
(Negative Acknowledgment) pattern, where vulnerabilities with Root IO
qualifier only match Root IO patched packages.
Implementation:
The Root IO qualifier ensures that Root IO vulnerabilities (from
api.root.io/external/osv) only match packages with rootio- prefix,
preventing false matches against standard upstream packages.