Skip to content

feat: add support for CSAF advisories with CPE-based product IDs instead of PURLs#2257

Open
Strum355 wants to merge 2 commits intoguacsec:mainfrom
Strum355:nsc/redhat-csaf-remediations
Open

feat: add support for CSAF advisories with CPE-based product IDs instead of PURLs#2257
Strum355 wants to merge 2 commits intoguacsec:mainfrom
Strum355:nsc/redhat-csaf-remediations

Conversation

@Strum355
Copy link
Member

@Strum355 Strum355 commented Feb 24, 2026

Summary by Sourcery

Extend vulnerability analysis to include CSAF product_status data with CPE-based product identifiers alongside existing PURL-based matching.

New Features:

  • Support resolving vulnerabilities from CSAF product_status entries that reference packages by CPE-based product context rather than only by PURL.
  • Expose CPE context on purl status results derived from CSAF advisories, including associated version ranges and remediations.

Enhancements:

  • Pre-fetch and cache CPE entities during analysis to efficiently enrich advisory results with CPE context metadata.
  • Combine PURL-based and product_status-based vulnerability queries into a unified analysis path for requested packages.

Tests:

  • Add integration tests covering CSAF advisories that use product_status with CPE-based product identifiers, validating affected status, CPE context, and remediation categories for Maven packages with and without namespaces.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 24, 2026

Reviewer's Guide

Extends vulnerability analysis to incorporate CSAF product_status entries that reference products by CPE instead of PURL, by prefetching CPE records, expanding SQL to union product_status-based advisories, threading CPE context through the analysis pipeline, and adding tests to validate behavior on real CSAF documents.

Sequence diagram for CPE-aware vulnerability analysis pipeline

sequenceDiagram
    actor Client
    participant VulnerabilityService
    participant DB as Database
    participant CPEMap as cpe_map
    participant PurlStatus

    Client->>VulnerabilityService: analyze_vulnerabilities(connection, purls)
    Note over VulnerabilityService: Fetch purl_status and product_status data
    VulnerabilityService->>DB: SELECT ... FROM purl_status ...
    DB-->>VulnerabilityService: purl_status rows (JSON advisories)
    VulnerabilityService->>DB: SELECT ... FROM product_status LEFT JOIN cpe ...
    DB-->>VulnerabilityService: product_status rows (JSON advisories with context_cpe)

    Note over VulnerabilityService: Collect IDs from JSONB advisories
    VulnerabilityService->>VulnerabilityService: extract advisory_id, context_cpe into sets
    VulnerabilityService->>DB: SELECT * FROM advisory WHERE id = ANY(advisory_ids)
    DB-->>VulnerabilityService: advisory models
    VulnerabilityService->>DB: SELECT * FROM cpe WHERE id = ANY(cpe_ids)
    DB-->>VulnerabilityService: cpe models
    VulnerabilityService->>CPEMap: build HashMap<Uuid, cpe_Model>

    Note over VulnerabilityService: Build analysis details per PURL
    loop for each purl
        VulnerabilityService->>VulnerabilityService: deserialize AdvisoryEntryDetailsPhase (includes context_cpe)
        alt context_cpe present
            VulnerabilityService->>CPEMap: lookup cpe_model by id
            CPEMap-->>VulnerabilityService: cpe_Model
            VulnerabilityService->>VulnerabilityService: cpe_string = cpe_Model.to_string()
        else no context_cpe
            VulnerabilityService->>VulnerabilityService: cpe_string = None
        end
        VulnerabilityService->>PurlStatus: from_head(head, advisory_head, purl, status, version_range, cpe_string, scores)
        PurlStatus-->>VulnerabilityService: PurlStatus instance
    end

    VulnerabilityService-->>Client: AnalysisData and AnalysisDetailsV3
Loading

Class diagram for updated vulnerability analysis data flow with CPE support

classDiagram
    class VulnerabilityService {
        +analyze_vulnerabilities(connection, purls) Result~AnalysisData~
        +build_analysis_details_v3(connection, head, purl, descriptions_map, scores_map, advisories_map, cpe_map) Result~tuple~
    }

    class AnalysisData {
        +purls_with_vulnerabilities : Vec
        +warnings : Vec
        +descriptions_map : HashMap~String, vulnerability_description_Model~
        +scores : Vec~advisory_vulnerability_score_Model~
        +advisories_map : HashMap~Uuid, AdvisoryData~
        +cpe_map : HashMap~Uuid, cpe_Model~
    }

    class AdvisoryEntryQueryPhase {
        +advisory_id : Uuid
        +context_cpe : Option~Uuid~
    }

    class AdvisoryEntryDetailsPhase {
        +status : String
        +advisory_id : Uuid
        +version_range : VersionRange
        +remediations : Vec~RemediationEntry~
        +context_cpe : Option~Uuid~
    }

    class cpe_Model {
        +id : Uuid
        +to_string() String
    }

    class PurlStatus {
        +from_head(head, advisory_head, purl, status, version_range, context_cpe_string, scores) Result~PurlStatus~
    }

    class AdvisoryData {
        <<struct>>
    }

    class VersionRange {
        <<struct>>
    }

    class RemediationEntry {
        <<struct>>
    }

    class AnalysisDetailsV3 {
        <<struct>>
    }

    class AnalysisPurlStatus {
        +purl_status : PurlStatus
    }

    VulnerabilityService --> AnalysisData : builds
    VulnerabilityService --> AnalysisDetailsV3 : builds
    AnalysisData --> cpe_Model : uses as map values
    AnalysisData --> AdvisoryData : uses as map values
    VulnerabilityService --> AdvisoryEntryQueryPhase : deserializes from JSONB
    VulnerabilityService --> AdvisoryEntryDetailsPhase : deserializes from JSONB
    VulnerabilityService --> cpe_Model : prefetches
    VulnerabilityService --> PurlStatus : constructs via from_head
    PurlStatus --> cpe_Model : optional context via to_string
    AnalysisDetailsV3 --> AnalysisPurlStatus : contains
    AnalysisPurlStatus --> PurlStatus : wraps
Loading

Flow diagram for building combined purl_status and product_status SQL

flowchart TD
    A[Start: build SQL for a requested PURL] --> B[Determine namespace condition for base_purl]
    B --> C[Build purl_status_sql string
SELECT ... FROM purl_status ...]
    C --> D[Create purl_status_query using Statement::from_sql_and_values
with requested_purl, name, type, version]

    D --> E[Determine package_condition
- if namespace present: product_status.package = full_name OR name
- else: product_status.package = name]

    E --> F[Build product_status_sql string
SELECT ... FROM product_status
JOIN status, vulnerability, product_version_range, version_range
LEFT JOIN cpe ON product_status.context_cpe_id = cpe.id
WHERE package_condition AND product_status.package IS NOT NULL
AND status.slug NOT IN fixed, not_affected, recommended]

    F --> G[Create product_status_query using Statement::from_sql_and_values
with requested_purl]

    G --> H[Concatenate SQL
final_sql = purl_status_query UNION ALL product_status_query]
    H --> I[Return final_sql for execution]
Loading

File-Level Changes

Change Details Files
Pre-fetch and thread CPE models through vulnerability analysis pipeline so CPE context can be attached to purl statuses.
  • Extend AnalysisData to include a CPE ID to model map and populate it when analyzing PURLs.
  • Parse optional context_cpe UUIDs from advisory JSON entries and collect unique CPE IDs.
  • Query CPE entities by collected IDs and store them in a HashMap keyed by ID for later lookup.
  • Pass the CPE map into analyze_purl_v3 and related helpers, and use it to render a human-readable CPE string on each PurlStatus when context_cpe is present.
modules/fundamental/src/vulnerability/service/mod.rs
Augment SQL used in analyze_purls_v3 so CSAF product_status entries are considered alongside legacy PURL-based advisories, including CPE context and remediations.
  • Refactor existing PURL-based SQL into purl_status_sql and keep its behavior intact.
  • Introduce a package_condition fragment that matches product_status.package against either full namespace/name or bare name depending on whether the PURL has a namespace.
  • Add a new product_status_sql query that joins product_status, status, vulnerability, product_version_range, version_range, remediation_product_status, remediation, and CPE to build advisories JSON including status, advisory_id, context_cpe, version_range, and remediations.
  • Return a UNION ALL of the PURL-based and product_status-based queries so both advisory sources contribute to the analysis.
modules/fundamental/src/vulnerability/service/mod.rs
Extend advisory JSON decoding and PurlStatus construction to carry CPE context and support product_status-backed advisories.
  • Add context_cpe field to AdvisoryEntry used when decoding advisories JSON in analyze_purl_v3.
  • Lookup the CPE string via the CPE map using context_cpe and pass it into PurlStatus::from_head as the context argument instead of always passing None.
  • Ensure score calculation paths ignore the CPE map where not needed by destructuring it as unused in those code paths.
modules/fundamental/src/vulnerability/service/mod.rs
Add integration tests covering product_status-based vulnerability resolution, CPE context, and remediations for specific CSAF documents.
  • Add analyze_purls_product_status test that ingests CSAF documents for CVE-2023-20862 and CVE-2023-0044, then asserts affected statuses, presence of CPE context, and expected remediation categories for spring-security and io.quarkus/quarkus-vertx-http packages.
  • Add analyze_purls_product_status_0044 test that focuses on a namespaced io.quarkus/quarkus-vertx-http PURL and verifies it yields CVE-2023-0044 details and exactly two remediations from product_status contexts.
modules/fundamental/src/vulnerability/service/test.rs

Possibly linked issues

  • #CSAF Remediation - product statuses: The PR implements analyze endpoint support for CSAF product_status, CPE context, and remediations exactly as described in the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The dynamic SQL composition for package_condition and product_status_sql relies on Statement::to_string() and string interpolation, which is brittle and backend-specific; consider using SeaORM's query builder (or consistently tracked parameter lists) to avoid subtle issues with placeholders and SQL injection/escaping.
  • There is a println! left in analyze_purls_product_status that prints remediations during the test run; consider removing or converting this to a log call to keep test output clean.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The dynamic SQL composition for `package_condition` and `product_status_sql` relies on `Statement::to_string()` and string interpolation, which is brittle and backend-specific; consider using SeaORM's query builder (or consistently tracked parameter lists) to avoid subtle issues with placeholders and SQL injection/escaping.
- There is a `println!` left in `analyze_purls_product_status` that prints remediations during the test run; consider removing or converting this to a `log` call to keep test output clean.

## Individual Comments

### Comment 1
<location path="modules/fundamental/src/vulnerability/service/mod.rs" line_range="537-546" />
<code_context>
                 );

-                Ok(Some(query.to_string()))
+                let package_condition = match &purl.namespace {
+                    Some(namespace) => {
+                        let full_name = format!("{}/{}", namespace, purl.name);
+                        let sql = "(product_status.package = $1 OR product_status.package = $2)";
+                        Statement::from_sql_and_values(
+                            connection.get_database_backend(),
+                            sql,
+                            [full_name.into(), purl.name.clone().into()],
+                        )
+                        .to_string()
+                    }
+                    None => {
</code_context>
<issue_to_address>
**issue (bug_risk):** Building `package_condition` via `Statement::to_string()` is brittle and may silently depend on SeaORM’s debug formatting/parameter inlining behavior.

This approach couples `package_condition` to SeaORM’s `Statement::to_string()` debug behavior and its current choice to inline bound parameters. If SeaORM changes `to_string()` to keep `$1`/`$2` placeholders, `product_status_sql` will contain unbound parameters since `product_status_query` only supplies `[p.into()]`, leading to runtime errors or parameter mismatches.

Prefer either building `package_condition` as plain SQL with explicit, safely-escaped interpolations, or representing the entire `product_status_sql` as a single `Statement` with all bindings managed together, rather than embedding a stringified `Statement`.
</issue_to_address>

### Comment 2
<location path="modules/fundamental/src/vulnerability/service/test.rs" line_range="933-939" />
<code_context>
+        "All statuses should be affected"
+    );
+
+    // Should have CPE context on statuses
+    assert!(
+        details
+            .purl_statuses
+            .iter()
+            .any(|s| s.purl_status.context.is_some()),
+        "Should have CPE context from product_status"
+    );
+
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen CPE context assertions to verify the actual CPE value, not only presence

This assertion only checks that *some* `purl_status.context` is `Some`, so it would still pass with the wrong CPE or a changed format. To properly validate the new `context_cpe` wiring, assert that the returned `context` values include the specific expected CPE string(s) from the CSAF (e.g. compare against a known `"cpe:2.3:..."`), so the `cpe_map` join and `PurlStatus` serialization are actually verified.

Suggested implementation:

```rust
    // All statuses should be "affected" (from known_affected)
    assert!(
        details
            .purl_statuses
            .iter()
            .all(|s| s.purl_status.status == "affected"),
        "All statuses should be affected"
    );

    // Should have CPE context on statuses and it should match the expected CPE(s)
    let contexts: Vec<&str> = details
        .purl_statuses
        .iter()
        .filter_map(|s| s.purl_status.context.as_deref())
        .collect();

    assert!(
        !contexts.is_empty(),
        "Expected at least one PurlStatus to have CPE context"
    );

    // Verify that the expected CPE from the CSAF is present in the contexts.
    // NOTE: replace the placeholder CPE string with the specific value from the CSAF fixture.
    let expected_cpe = "cpe:2.3:REPLACE_ME_WITH_EXPECTED_CPE_FROM_CSAF";
    assert!(
        contexts.iter().any(|c| *c == expected_cpe),
        "Expected CPE context `{}` not found in PurlStatus contexts: {:?}",
        expected_cpe,
        contexts
    );


```

To fully implement the intent of the review comment, update the `expected_cpe` placeholder string to the actual CPE value present in the `csaf/CVE-2023-20862.json` (or the relevant CSAF fixture for this test).  
If multiple distinct CPEs are expected, you can extend this check by:
1. Defining a `Vec<&str>` or `HashSet<&str>` of expected CPEs.
2. Asserting that each expected CPE is contained in `contexts` (e.g. with `all(|expected| contexts.contains(expected))`).
</issue_to_address>

### Comment 3
<location path="modules/fundamental/src/vulnerability/service/test.rs" line_range="1033-1038" />
<code_context>
+    let details = &analysis.details[0];
+    assert_eq!(details.head.identifier, "CVE-2023-0044");
+
+    let remediations = details
+        .purl_statuses
+        .iter()
+        .flat_map(|status| status.remediations.clone())
+        .collect_vec();
+    assert_eq!(remediations.len(), 2);
+
+    Ok(())
</code_context>
<issue_to_address>
**suggestion (testing):** Make the remediation assertions more specific than just counting entries

Here, the test only checks that there are 2 remediations, so it would still pass if their categories or contents changed. Since this test is specific to `cve-2023-0044.json`, please also assert the expected remediation categories (e.g., `Workaround` and `NoneAvailable`) and, if possible, that key fields like `details` or `url` are present. That will better guard the product_status→remediation mapping for namespaced packages.

Suggested implementation:

```rust
    let details = &analysis.details[0];
    assert_eq!(details.head.identifier, "CVE-2023-0044");

    let remediations = details
        .purl_statuses
        .iter()
        .flat_map(|status| status.remediations.iter())
        .collect_vec();
    assert_eq!(remediations.len(), 2, "Expected exactly two remediations for CVE-2023-0044");

    // Ensure we have the expected remediation categories for this CSAF document
    let categories = remediations
        .iter()
        .map(|remediation| &remediation.category)
        .collect_vec();

    assert!(
        categories.iter().any(|c| matches!(c, RemediationCategory::Workaround)),
        "Expected at least one remediation with category Workaround"
    );
    assert!(
        categories.iter().any(|c| matches!(c, RemediationCategory::NoneAvailable)),
        "Expected at least one remediation with category NoneAvailable"
    );

    // Ensure each remediation has some meaningful content attached
    for remediation in remediations {
        assert!(
            remediation.details.is_some() || remediation.url.is_some(),
            "Each remediation should have either details text or a URL"
        );
    }

```

You may need to:
1. Import or qualify the `RemediationCategory` enum at the top of this file, for example:
   `use trustify_csaf::model::RemediationCategory;`
   or whatever path is used elsewhere in the codebase.
2. Adjust the field names `details` and `url` on the remediation struct if they differ
   (e.g. `description` instead of `details`), so that the assertions compile correctly.
3. If `status.remediations` is already a slice or reference type, you can keep using
   `.clone()` as in the original code; I switched to iterating by reference with
   `.iter()` to avoid unnecessary cloning, but you can align this with the existing
   conventions in the file.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +933 to +939
// Should have CPE context on statuses
assert!(
details
.purl_statuses
.iter()
.any(|s| s.purl_status.context.is_some()),
"Should have CPE context from product_status"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Strengthen CPE context assertions to verify the actual CPE value, not only presence

This assertion only checks that some purl_status.context is Some, so it would still pass with the wrong CPE or a changed format. To properly validate the new context_cpe wiring, assert that the returned context values include the specific expected CPE string(s) from the CSAF (e.g. compare against a known "cpe:2.3:..."), so the cpe_map join and PurlStatus serialization are actually verified.

Suggested implementation:

    // All statuses should be "affected" (from known_affected)
    assert!(
        details
            .purl_statuses
            .iter()
            .all(|s| s.purl_status.status == "affected"),
        "All statuses should be affected"
    );

    // Should have CPE context on statuses and it should match the expected CPE(s)
    let contexts: Vec<&str> = details
        .purl_statuses
        .iter()
        .filter_map(|s| s.purl_status.context.as_deref())
        .collect();

    assert!(
        !contexts.is_empty(),
        "Expected at least one PurlStatus to have CPE context"
    );

    // Verify that the expected CPE from the CSAF is present in the contexts.
    // NOTE: replace the placeholder CPE string with the specific value from the CSAF fixture.
    let expected_cpe = "cpe:2.3:REPLACE_ME_WITH_EXPECTED_CPE_FROM_CSAF";
    assert!(
        contexts.iter().any(|c| *c == expected_cpe),
        "Expected CPE context `{}` not found in PurlStatus contexts: {:?}",
        expected_cpe,
        contexts
    );

To fully implement the intent of the review comment, update the expected_cpe placeholder string to the actual CPE value present in the csaf/CVE-2023-20862.json (or the relevant CSAF fixture for this test).
If multiple distinct CPEs are expected, you can extend this check by:

  1. Defining a Vec<&str> or HashSet<&str> of expected CPEs.
  2. Asserting that each expected CPE is contained in contexts (e.g. with all(|expected| contexts.contains(expected))).

Comment on lines +1033 to +1038
let remediations = details
.purl_statuses
.iter()
.flat_map(|status| status.remediations.clone())
.collect_vec();
assert_eq!(remediations.len(), 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Make the remediation assertions more specific than just counting entries

Here, the test only checks that there are 2 remediations, so it would still pass if their categories or contents changed. Since this test is specific to cve-2023-0044.json, please also assert the expected remediation categories (e.g., Workaround and NoneAvailable) and, if possible, that key fields like details or url are present. That will better guard the product_status→remediation mapping for namespaced packages.

Suggested implementation:

    let details = &analysis.details[0];
    assert_eq!(details.head.identifier, "CVE-2023-0044");

    let remediations = details
        .purl_statuses
        .iter()
        .flat_map(|status| status.remediations.iter())
        .collect_vec();
    assert_eq!(remediations.len(), 2, "Expected exactly two remediations for CVE-2023-0044");

    // Ensure we have the expected remediation categories for this CSAF document
    let categories = remediations
        .iter()
        .map(|remediation| &remediation.category)
        .collect_vec();

    assert!(
        categories.iter().any(|c| matches!(c, RemediationCategory::Workaround)),
        "Expected at least one remediation with category Workaround"
    );
    assert!(
        categories.iter().any(|c| matches!(c, RemediationCategory::NoneAvailable)),
        "Expected at least one remediation with category NoneAvailable"
    );

    // Ensure each remediation has some meaningful content attached
    for remediation in remediations {
        assert!(
            remediation.details.is_some() || remediation.url.is_some(),
            "Each remediation should have either details text or a URL"
        );
    }

You may need to:

  1. Import or qualify the RemediationCategory enum at the top of this file, for example:
    use trustify_csaf::model::RemediationCategory;
    or whatever path is used elsewhere in the codebase.
  2. Adjust the field names details and url on the remediation struct if they differ
    (e.g. description instead of details), so that the assertions compile correctly.
  3. If status.remediations is already a slice or reference type, you can keep using
    .clone() as in the original code; I switched to iterating by reference with
    .iter() to avoid unnecessary cloning, but you can align this with the existing
    conventions in the file.

@codecov
Copy link

codecov bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 97.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.51%. Comparing base (390d67a) to head (27776f2).
⚠️ Report is 34 commits behind head on main.

Files with missing lines Patch % Lines
...dules/fundamental/src/vulnerability/service/mod.rs 96.55% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2257      +/-   ##
==========================================
+ Coverage   69.08%   70.51%   +1.42%     
==========================================
  Files         407      413       +6     
  Lines       23047    23943     +896     
  Branches    23047    23943     +896     
==========================================
+ Hits        15922    16883     +961     
+ Misses       6231     6142      -89     
- Partials      894      918      +24     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Strum355
Copy link
Member Author

Strum355 commented Feb 24, 2026

Output from /v3/vulnerability/analyze output given https://github.com/guacsec/trustify/blob/main/etc/test-data/csaf/cve-2023-0044.json document for the purl pkg:maven/io.quarkus/quarkus-vertx-http@1.0.0:

{
  "vulnerability": {
    "normative": true,
    "identifier": "CVE-2023-0044",
    "title": null,
    "description": null,
    "reserved": null,
    "published": null,
    "modified": null,
    "withdrawn": null,
    "discovered": null,
    "released": null,
    "cwes": []
  },
  "advisory": {
    "uuid": "urn:uuid:dc6d3239-ddaa-4241-81ad-910dfe143d0e",
    "identifier": "https://www.redhat.com/#CVE-2023-0044",
    "document_id": "CVE-2023-0044",
    "issuer": {
      "id": "88d35642-663a-4daa-9982-590d74a7409d",
      "name": "Red Hat Product Security",
      "cpe_key": null,
      "website": null
    },
    "published": "2023-01-04T00:00:00Z",
    "modified": "2023-11-13T11:31:31Z",
    "withdrawn": null,
    "title": "quarkus-vertx-http: a cross-site attack may be initiated which might lead to the Information Disclosure",
    "labels": {
      "source": "TrustifyContext",
      "type": "csaf"
    }
  },
  "scores": [
    {
      "type": "3.1",
      "value": 5.3,
      "severity": "medium"
    }
  ],
  "average_severity": "medium",
  "average_score": 5.3,
  "status": "affected",
  "context": {
    "cpe": "cpe:/a:redhat:quarkus:2:*:*:*"
  },
  "version_range": {
    "version_scheme_id": "rpm",
    "low_version": "2.0.0",
    "low_inclusive": true,
    "high_version": "3.0.0",
    "high_inclusive": false
  },
  "remediations": [
    {
      "id": "b8e80493-7c71-5e98-a584-9cd0fc4434d5",
      "category": "workaround",
      "details": "This attack can be prevented with the Quarkus CSRF Prevention feature.",
      "url": null,
      "data": {
        "details": "This attack can be prevented with the Quarkus CSRF Prevention feature.",
        "category": "workaround",
        "product_ids": [
          "Red Hat build of Quarkus",
          "Red Hat build of Quarkus 2.7.7",
          "a-mq_clients_2:quarkus-vertx-http",
          "red_hat_build_of_quarkus:io.quarkus/quarkus-vertx-http",
          "red_hat_integration_camel_k:quarkus-vertx-http",
          "red_hat_integration_camel_quarkus:quarkus-vertx-http",
          "red_hat_integration_change_data_capture:quarkus-vertx-http",
          "red_hat_integration_service_registry:quarkus-vertx-http",
          "red_hat_jboss_enterprise_application_platform_7:quarkus-vertx-http",
          "red_hat_jboss_enterprise_application_platform_expansion_pack:quarkus-vertx-http",
          "red_hat_jboss_fuse_7:quarkus-vertx-http",
          "red_hat_process_automation_7:quarkus-vertx-http"
        ]
      }
    },
    {
      "id": "e2826a19-8598-5e67-bab2-4fe1a4c33583",
      "category": "none_available",
      "details": "Affected",
      "url": null,
      "data": {
        "details": "Affected",
        "category": "none_available",
        "product_ids": [
          "red_hat_build_of_quarkus:io.quarkus/quarkus-vertx-http"
        ]
      }
    }
  ]
}

@ctron
Copy link
Contributor

ctron commented Feb 27, 2026

Sorry for asking maybe basic questions, I wasn't part of the discussions up until now. I'd like to understand the motivation.

If the user provides an SBOM (most likely, of their own project), which contains a package pkg:maven/io.quarkus/quarkus-vertx-http@1.0.0, the user would get back the information, that the product red_hat_jboss_fuse_7:quarkus-vertx-http is affected, but a workaround exists.

I am not sure, that this is what users are looking for. So, is that the intention?

@Strum355
Copy link
Member Author

If the user provides an SBOM (most likely, of their own project), which contains a package pkg:maven/io.quarkus/quarkus-vertx-http@1.0.0, the user would get back the information, that the product red_hat_jboss_fuse_7:quarkus-vertx-http is affected, but a workaround exists.

I am not sure, that this is what users are looking for. So, is that the intention?

The data field is intended to be denormalized/raw underlying data, I brought this up in the original Remedations ADR discussion here for some brief context for you: #2179 (comment)

@ctron
Copy link
Contributor

ctron commented Mar 2, 2026

Thanks for linking the PR, that should help me understanding things.

@ctron
Copy link
Contributor

ctron commented Mar 2, 2026

If the user provides an SBOM (most likely, of their own project), which contains a package pkg:maven/io.quarkus/quarkus-vertx-http@1.0.0, the user would get back the information, that the product red_hat_jboss_fuse_7:quarkus-vertx-http is affected, but a workaround exists.
I am not sure, that this is what users are looking for. So, is that the intention?

The data field is intended to be denormalized/raw underlying data, I brought this up in the original Remedations ADR discussion here for some brief context for you: #2179 (comment)

After reading this, I'm still not sure that this what the user wants. But to my understanding you all discussed this and this is what we want.

@ctron
Copy link
Contributor

ctron commented Mar 2, 2026

It looks like the PR does some significant changes on database queries. So I want to be sure to understand the performance impact on this.

Which endpoints would be affected by this? And do we have tests in scale testing (https://github.com/guacsec/trustify-scale-testing/blob/main/src/restapi.rs) covering them? If not, we should add them first. As those endpoints seem to exist already. This PR is just adding more data to it.

Copy link
Contributor

@ctron ctron Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is getting a bit too big. We should start splitting it up.


let product_status_sql = format!(
r#"
SELECT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of code duplication with the previous SQL statement. It should be possible to reduce that by introducing a common function, helping to build that statement.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive tried to address this in the below commit, let me know if this works for you or youd rather a different approach?
e32ad98


#[test_context(TrustifyContext)]
#[test(tokio::test)]
async fn analyze_purls_product_status(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to have a quick summary of with both tests are supposed to test.


#[test_context(TrustifyContext)]
#[test(tokio::test)]
async fn analyze_purls_product_status(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not transform those tests into endpoint tests? Covering the endpoint handling as well.

@ctron
Copy link
Contributor

ctron commented Mar 2, 2026

I just tried the PR, and peeked at the response from the service function. I'm struggling to understand the output. Which might also indicate that the tests need to be firmed up a bit. Not only checked if expected information is in there. But also that non-expected information is not part. Maybe just falling back to a simple pattern of result == json!({}).

Running analyze_purls_product_status, I get back:

For pkg:maven/spring-security@1.0.0, an entry of CVE-2023-20862, having an advisory of https://www.redhat.com/#CVE-2023-20862", which claims a CPE of cpe:/a:redhat:jboss_fuse_service_works:6:*:*:*.

For remediations of this entry, I get:

data: Object {
    "details": String("Out of support scope"),
    "category": String("no_fix_planned"),
    "product_ids": Array [
        String("red_hat_decision_manager_7:spring-security"),
        String("red_hat_jboss_a-mq_6:spring-security"),
        String("red_hat_jboss_enterprise_application_platform_6:spring-security"),
        String("red_hat_jboss_fuse_6:spring-security"),
        String("red_hat_jboss_fuse_7:spring-security"),
        String("red_hat_jboss_fuse_service_works_6:spring-security"),
        String("red_hat_openshift_container_platform_3.11:jenkins"),
        String("red_hat_process_automation_7:spring-security"),
    ],
},

I don't understand, why I get red_hat_openshift_container_platform_3.11:jenkins for cpe:/a:redhat:jboss_fuse_service_works:6:*:*:*.

cve-2023-0044.json doesn't seem to be relevant for this part.

CVE-2023-20862.json seems to be. However, to my understanding, it says that jenkins is affected:

image

jenkins is a component of openshift_developer_tools_and_services, which has a CPE of cpe:/a:redhat:ocp_tools.

So it looks like something doesn't add up here. Or maybe I don't understand it.

@Strum355
Copy link
Member Author

Strum355 commented Mar 5, 2026

Running analyze_purls_product_status, I get back:

For pkg:maven/spring-security@1.0.0, an entry of CVE-2023-20862, having an advisory of https://www.redhat.com/#CVE-2023-20862", which claims a CPE of cpe:/a:redhat:jboss_fuse_service_works:6:*:*:*.

For remediations of this entry, I get:

data: Object {
    "details": String("Out of support scope"),
    "category": String("no_fix_planned"),
    "product_ids": Array [
        String("red_hat_decision_manager_7:spring-security"),
        String("red_hat_jboss_a-mq_6:spring-security"),
        String("red_hat_jboss_enterprise_application_platform_6:spring-security"),
        String("red_hat_jboss_fuse_6:spring-security"),
        String("red_hat_jboss_fuse_7:spring-security"),
        String("red_hat_jboss_fuse_service_works_6:spring-security"),
        String("red_hat_openshift_container_platform_3.11:jenkins"),
        String("red_hat_process_automation_7:spring-security"),
    ],
},

I don't understand, why I get red_hat_openshift_container_platform_3.11:jenkins for cpe:/a:redhat:jboss_fuse_service_works:6:*:*:*.

jenkins is a component of openshift_developer_tools_and_services, which has a CPE of cpe:/a:redhat:ocp_tools.

If Im reading the CSAF document right (which I may not be), the remediation linkage seems correct: The reason red_hat_openshift_container_platform_3.11:jenkins appears is because the data field is the raw CSAF remediation JSON from the CSAF document. In the CSAF document, a single remediation entry lists all product_ids it covers (including jenkins), so the raw blob contains them all (link to the specific excerpt in the document here: https://github.com/Strum355/trustify/blob/nsc/redhat-csaf-remediations/etc/test-data/csaf/CVE-2023-20862.json#L670-L683).

Which endpoints would be affected by this? And do we have tests in scale testing (https://github.com/guacsec/trustify-scale-testing/blob/main/src/restapi.rs) covering them? If not, we should add them first. As those endpoints seem to exist already. This PR is just adding more data to it.

The affected endpoints should be /api/v3/purl/recommend and /api/v2/vulnerability/analyze. The former doesn't have any scale tests currently (Ill go ahead and add some), the latter has scale tests (https://github.com/guacsec/trustify-scale-testing/blob/main/src/restapi.rs#L355C33-L368)

Thanks for the review feedback so far! @ctron

@ctron
Copy link
Contributor

ctron commented Mar 10, 2026

So, to my understanding, the CSAF document contains a list of vulnerabilities. And each vulnerability lists:

  • a status in the context of a product/component
  • remediations for products/components

So the data is modeled in a way that makes is easy to transport information, for the use case of CSAF.

However, that doesn't mean that just returning the whole section makes sense.

3.2.3.12.6 Vulnerabilities Property - Remediations - Product IDs

Product IDs (product_ids) are of value type Products (products_t) and contain a list of Products the current remediation item applies to.

This field defines for which product IDs this remediation entry relates to. However, when returning a remediation entry for "component A", it doesn't seem to make sense to return the information that the same remediation also applies to "component B".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants