fix(purl): filter purl_statuses by SBOM describing CPEs (TC-5171)#2523
Open
ruromero wants to merge 5 commits into
Open
fix(purl): filter purl_statuses by SBOM describing CPEs (TC-5171)#2523ruromero wants to merge 5 commits into
ruromero wants to merge 5 commits into
Conversation
…oss-product matches The purl_status query in PurlDetails was returning entries with context_cpe_id values from unrelated products (e.g., RPM/RHEL CPE contexts for Maven packages). This happened because context_cpe_id was never validated against the SBOM's own describing CPEs. Add a three-way filter matching the pattern already used in raw_sql.rs (batch_severity_counts_sql): allow purl_status rows where context_cpe_id is NULL, or matches a CPE from the SBOM's describing CPEs (via sbom_describing_cpe), or when the SBOM has no describing CPEs at all (fallback). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
Reviewer's GuideThis PR updates the PurlDetails SBOM-backed query so that purl_status results are filtered by the SBOMs that describe the package’s CPEs, preventing cross-product advisory false positives while preserving backward compatibility when no describing CPEs exist. Entity relationship diagram for SBOM-based purl_status CPE filteringerDiagram
sbom {
int id
}
qualified_purl {
int id
}
sbom_node_purl_ref {
int sbom_id
int qualified_purl_id
}
sbom_describing_cpe {
int sbom_id
int cpe_id
}
cpe {
int id
}
base_purl {
int id
}
purl_status {
int base_purl_id
int context_cpe_id
}
qualified_purl ||--o{ sbom_node_purl_ref : references
sbom ||--o{ sbom_node_purl_ref : has_nodes_for
sbom ||--o{ sbom_describing_cpe : describes
cpe ||--o{ sbom_describing_cpe : is_described_in
base_purl ||--o{ purl_status : has_status
cpe ||--o{ purl_status : context_cpe_id
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The three new subqueries (
sbom_ids_for_purl,allowed_cpe_ids,sbom_has_cpes) are evaluated independently per request; consider whether they can be folded into joins or a single EXISTS-based condition to reduce query complexity and potential performance impact at scale. - The fallback
Expr::exists(sbom_has_cpes).not()logic is subtle; it may be worth extracting this condition into a well-named helper or adding an inline comment near the filter to clarify that it intentionally passes all statuses when no describing CPEs exist.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The three new subqueries (`sbom_ids_for_purl`, `allowed_cpe_ids`, `sbom_has_cpes`) are evaluated independently per request; consider whether they can be folded into joins or a single EXISTS-based condition to reduce query complexity and potential performance impact at scale.
- The fallback `Expr::exists(sbom_has_cpes).not()` logic is subtle; it may be worth extracting this condition into a well-named helper or adding an inline comment near the filter to clarify that it intentionally passes all statuses when no describing CPEs exist.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2523 +/- ##
==========================================
+ Coverage 71.66% 71.70% +0.04%
==========================================
Files 453 457 +4
Lines 27517 27764 +247
Branches 27517 27764 +247
==========================================
+ Hits 19719 19909 +190
- Misses 6667 6703 +36
- Partials 1131 1152 +21 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…bility/analyze The build_query() function in the vulnerability service returns advisory entries with RPM/RHEL CPE contexts for non-RPM packages because context_cpe_id is never validated against the SBOM's describing CPEs. Add the three-way context_cpe_id filter to both purl_status and product_status sub-queries in build_query(): 1. context_cpe_id IS NULL (advisory applies globally) 2. context_cpe_id matches an SBOM describing CPE for the purl 3. The purl has no SBOM with describing CPEs (fallback) For purl_status, base_purl.id is available in the FROM clause so the correlated subquery joins through versioned_purl directly. For product_status, base_purl is not in scope, so a product_bp_condition variable is built using Statement::from_sql_and_values to match by name/type/namespace, with an explicit JOIN to base_purl in the subquery. This complements commit 81669e1 which applied the same fix to the purl details endpoint (purl.rs). Fixes: guacsec#2524 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The purl details endpoint filtered purl_statuses using exact CPE ID matching against SBOM describing CPEs. Advisory context CPEs use different version granularity than SBOM describing CPEs (e.g., version=8 vs version=8.8), causing all entries to be filtered out. Add a UNION with generalized CPE matching that expands allowed CPE IDs to include those sharing (vendor, product, split_part(version, '.', 1)) with relaxed edition, matching the pattern already used in raw_sql.rs. This completes the fix across all three affected code paths (raw_sql.rs, build_query, and purl details). Refs: TC-5171 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The build_query() function for /vulnerability/analyze filtered purl_statuses and product_statuses using exact CPE ID matching. Advisory context CPEs use different version granularity than SBOM describing CPEs, causing all entries to be filtered out. Add UNION with generalized CPE matching to both the purl_status and product_status subqueries, expanding allowed CPE IDs to include those sharing (vendor, product, split_part(version, '.', 1)) with relaxed edition. Refs: TC-5171 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The edition constraint (edition IS NULL OR edition = '*') in the generalized CPE matching was too strict — it excluded legitimate same-product entries with specific editions (e.g. baseos when SBOM describes appstream). All CPEs sharing the same vendor, product, and major version should be included regardless of edition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Filter
purl_statusesand/vulnerability/analyzeresults to exclude entries whosecontext_cpe_iddoes not match the SBOM's describing CPEs. Uses generalized CPE matching so that all CPEs sharing the same vendor, product, and major version are included in the allowed set — this correctly handles cases where the SBOM describes one edition (e.g.appstream) but advisories reference another edition of the same product (e.g.baseos).Problem
When querying vulnerability data for a package,
purl_statusesand/vulnerability/analyzereturned entries with CPE contexts from unrelated products (e.g., JBoss, RHSCL) becausecontext_cpe_idwas never validated against the SBOM's describing CPEs. This caused thousands of false-positive vulnerability rows in large SBOM analyses.Fix
1. Three-way
context_cpe_idfilterAdded to all three query paths (SeaORM purl endpoint, raw SQL analyze endpoint, raw SQL SBOM batch counts):
context_cpe_id IS NULL— always allow statuses without a CPE contextcontext_cpe_id IN (allowed CPEs)— allow statuses whose CPE matches the SBOM's describing CPEs (with generalized matching)2. Generalized CPE matching
The allowed CPE set is expanded beyond exact describing CPEs to include all CPEs sharing the same
(vendor, product, major_version). This handles the common case where an SBOM describesenterprise_linux:8::appstreambut advisory entries referenceenterprise_linux:8::baseos— both are the same product (RHEL 8) and should be included.Changes
modules/fundamental/src/purl/model/details/purl.rs: Added three subqueries (sbom_ids_for_purl,generalized_cpe_ids,sbom_has_cpes) and aCondition::any()filter to the SeaORMpurl_statusesquerymodules/fundamental/src/vulnerability/service/mod.rs: Added generalized CPE matching CTEs and three-way filter to bothbuild_query()variants (bybase_purl.idand by{product_bp_condition})modules/fundamental/src/sbom/model/raw_sql.rs: UpdatedCONTEXT_CPE_FILTER_SQL,batch_severity_counts_sql(), andproduct_advisory_info_sql()to use generalized matching without edition constraintsTesting
expat@2.2.5-11.el8(22 version-matching purl_status entries across 7+ products): 2 baseos entries correctly pass, 20 cross-product entries correctly filteredcargo clippyandcargo fmtpassFixes #2524
Fixes: TC-5171