Skip to content

fix(evaluator): create package linkages for all CVE types#2183

Open
mtclinton wants to merge 1 commit intoRedHatInsights:masterfrom
mtclinton:nodesjs-false-positive-fix
Open

fix(evaluator): create package linkages for all CVE types#2183
mtclinton wants to merge 1 commit intoRedHatInsights:masterfrom
mtclinton:nodesjs-false-positive-fix

Conversation

@mtclinton
Copy link
Contributor

@mtclinton mtclinton commented Jan 28, 2026

Secure Coding Practices Checklist GitHub Link

The evaluator was creating VULNERABLE_BY_PACKAGE entries but failing to create
corresponding system_vulnerable_package entries for playbook_cves and
manually_fixable_cves. This caused orphaned vulnerabilities where systems
were marked vulnerable but had no package linkages.

Now extracts package information from all CVE types and creates proper
package linkages with module stream information

Secure Coding Checklist

  • Input Validation
  • Output Encoding
  • Authentication and Password Management
  • Session Management
  • Access Control
  • Cryptographic Practices
  • Error Handling and Logging
  • Data Protection
  • Communication Security
  • System Configuration
  • Database Security
  • File Management
  • Memory Management
  • General Coding Practices

Summary by Sourcery

Ensure evaluator creates package linkages for all CVE types using extended VMAAS response data.

Bug Fixes:

  • Create system_vulnerable_package entries for playbook and manually-fixable CVEs by extracting and aggregating their package information.
  • Handle missing or partial package/module data from VMAAS more defensively to avoid orphan or invalid vulnerability records.

Enhancements:

  • Extend VMAAS evaluation to return explicit package data for playbook and manually-fixable CVEs and include it in unified vulnerability aggregation.
  • Derive missing module stream information for affected packages from the system module list when not provided by VMAAS.

@jira-linking
Copy link

jira-linking bot commented Jan 28, 2026

Referenced Jiras:
https://issues.redhat.com/browse/RHINENG-22053

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 28, 2026

Reviewer's Guide

Extends the evaluator’s VMAAS handling so that playbook and manually-fixable CVEs also produce package-level linkages (including module stream inference), preventing orphaned vulnerabilities where systems are marked vulnerable without associated packages.

Sequence diagram for updated VMAAS evaluation and package linkage creation

sequenceDiagram
    participant Evaluator
    participant VMAASService
    participant Database

    Evaluator->>VMAASService: vmaas_request(vmaas_vulnerabilities_endpoint, vmaas_json)
    VMAASService-->>Evaluator: vmaas_response

    alt vmaas_response missing package_list or repository_list
        Evaluator-->>Evaluator: return empty lists
    else vmaas_response valid
        Evaluator-->>Evaluator: _perform_vmaas_request
        loop build_playbook_and_manual_lists
            Evaluator-->>Evaluator: create CveAdvisories for playbook_cves
            Evaluator-->>Evaluator: create CveUnpatched for playbook_cve_packages
            Evaluator-->>Evaluator: create CveAdvisories for manually_fixable_cves
            Evaluator-->>Evaluator: create CveUnpatched for manually_fixable_cve_packages
        end
        loop build_unpatched_list
            Evaluator-->>Evaluator: create CveUnpatched for unpatched_cves
        end

        Evaluator-->>Evaluator: _evaluate_vmaas_res(..., playbook_cve_packages, manually_fixable_cve_packages)
        Evaluator-->>Evaluator: all_cve_packages = unpatched_cves + playbook_cve_packages + manually_fixable_cve_packages

        loop for each cve_package in all_cve_packages
            alt missing package_name or cpe
                Evaluator-->>Evaluator: skip cve_package
            else
                alt module info missing
                    Evaluator-->>Evaluator: _get_module_from_system(package_name, system_platform.vmaas_json)
                end
                Evaluator-->>Evaluator: _get_or_upsert_cve(cve)
                Evaluator-->>Evaluator: pn_cpes[(package_name, cpe, module_name, module_stream)] add cve_id
            end
        end

        loop for each (package_name, cpe, module_name, module_stream) in pn_cpes
            Evaluator-->>Evaluator: _get_or_upsert_vulnerable_package(...)
            Evaluator->>Database: upsert system_vulnerable_package rows
            Database-->>Evaluator: success
        end
    end
Loading

Updated class diagram for evaluator VMAAS CVE and package handling

classDiagram
    class Evaluator {
        +_perform_vmaas_request(vmaas_json: dict) Tuple~List~CveAdvisories~~,List~CveAdvisories~~,List~CveUnpatched~~,List~CveUnpatched~~,List~CveUnpatched~~~
        +_get_module_from_system(package_name: str, vmaas_json: dict) Tuple~Optional~str~~,Optional~str~~
        +_evaluate_vmaas_res(playbook_cves: List~CveAdvisories~, manually_fixable_cves: List~CveAdvisories~, unpatched_cves: List~CveUnpatched~, playbook_cve_packages: List~CveUnpatched~, manually_fixable_cve_packages: List~CveUnpatched~, sys_vuln_rows: dict, system_platform: SystemPlatform, conn: AsyncConnection) dict
        +evaluate_vulnerabilities(system_platform: SystemPlatform, conn: AsyncConnection)
        +_get_or_upsert_cve(cve_name: str) CveCache
        +_get_or_upsert_vulnerable_package(package_name_id: int, cpe_id: int, module_id: int) VulnerablePackage
    }

    class CveAdvisories {
        +cve: str
        +advisories: Optional~str~
    }

    class CveUnpatched {
        +cve: str
        +package_name: str
        +cpe: str
        +module_name: Optional~str~
        +module_stream: Optional~str~
    }

    class SystemPlatform {
        +vmaas_json: dict
    }

    class CveCache {
        +id: int
        +cve: str
    }

    class VulnerablePackage {
        +id: int
        +package_name_id: int
        +cpe_id: int
        +module_id: Optional~int~
    }

    class AsyncConnection

    Evaluator --> CveAdvisories : uses
    Evaluator --> CveUnpatched : uses
    Evaluator --> SystemPlatform : reads vmaas_json
    Evaluator --> CveCache : upserts
    Evaluator --> VulnerablePackage : upserts
    Evaluator --> AsyncConnection : database operations
    CveUnpatched --> SystemPlatform : module inference via vmaas_json
Loading

File-Level Changes

Change Details Files
Extend VMAAS request/response handling to collect package information for playbook and manually-fixable CVEs alongside existing unpatched CVEs.
  • Updated _perform_vmaas_request to return five lists: playbook_cves, manually_fixable_cves, unpatched_cves, playbook_cve_packages, manually_fixable_cve_packages.
  • Parsed cve_list and manually_fixable_cve_list to build CveAdvisories plus corresponding CveUnpatched entries from each CVE’s affected list.
  • Made unpatched_cve_list parsing more defensive using .get on affected/package fields and kept building CveUnpatched entries.
evaluator/logic.py
Unify evaluation of all CVE package data and ensure module information is present, inferring it from the system when missing, so system_vulnerable_package rows are created consistently.
  • Added helper _get_module_from_system to infer module_name and module_stream for a package from system_platform.vmaas_json.modules_list.
  • Extended _evaluate_vmaas_res signature and call sites to accept playbook_cve_packages and manually_fixable_cve_packages in addition to unpatched_cves.
  • Aggregated unpatched_cves, playbook_cve_packages, and manually_fixable_cve_packages into a single list, normalized missing module info via _get_module_from_system, and built pn_cpes and vuln_package_cve mappings from this combined data.
evaluator/logic.py

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

@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 2 issues, and left some high level feedback:

  • In _perform_vmaas_request, you construct CveUnpatched with package_name/cpe defaulting to empty strings and later skip entries where those fields are falsy; consider either skipping such entries at creation time or representing missing values as None so it's clearer when data is actually absent and you avoid creating objects that are immediately discarded.
  • The _get_module_from_system heuristic of matching modules by package_name.startswith(module_name) could easily mis-associate packages to modules with similar prefixes; it may be worth tightening this logic (e.g., delimiter-aware matching or explicit mapping) or at least constraining it to known-safe patterns to reduce incorrect module assignments.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_perform_vmaas_request`, you construct `CveUnpatched` with `package_name`/`cpe` defaulting to empty strings and later skip entries where those fields are falsy; consider either skipping such entries at creation time or representing missing values as `None` so it's clearer when data is actually absent and you avoid creating objects that are immediately discarded.
- The `_get_module_from_system` heuristic of matching modules by `package_name.startswith(module_name)` could easily mis-associate packages to modules with similar prefixes; it may be worth tightening this logic (e.g., delimiter-aware matching or explicit mapping) or at least constraining it to known-safe patterns to reduce incorrect module assignments.

## Individual Comments

### Comment 1
<location> `evaluator/logic.py:310-312` </location>
<code_context>
     @time(EVAL_PART_TIME.labels(part="vmaas_request"))
-    async def _perform_vmaas_request(self, vmaas_json: dict) -> (List[CveAdvisories], List[CveAdvisories], List[CveUnpatched]):
-        """Perform VMAAS request for package based evaluation"""
+    async def _perform_vmaas_request(
+        self, vmaas_json: dict
+    ) -> (List[CveAdvisories], List[CveAdvisories], List[CveUnpatched], List[CveUnpatched], List[CveUnpatched]):
+        """Perform VMAAS request for package based evaluation
+
</code_context>

<issue_to_address>
**suggestion:** Use a proper Tuple type for the multi-value return annotation instead of a bare parenthesized type list.

`-> (List[...], ...)` is parsed as just the last type, not a multi-value return. Please use a proper tuple annotation, e.g. `-> Tuple[List[CveAdvisories], List[CveAdvisories], List[CveUnpatched], List[CveUnpatched], List[CveUnpatched]]`, so type checkers and editors understand the structure and can catch regressions if the return shape changes.

Suggested implementation:

```python
    @time(EVAL_PART_TIME.labels(part="vmaas_request"))
    async def _perform_vmaas_request(
        self, vmaas_json: dict
    ) -> Tuple[
        List[CveAdvisories],
        List[CveAdvisories],
        List[CveUnpatched],
        List[CveUnpatched],
        List[CveUnpatched],
    ]:

```

1. Ensure `Tuple` is imported from `typing` at the top of `evaluator/logic.py`, e.g. extend an existing `from typing import ...` line to include `Tuple`.
2. If `List` and the CVE types are not already imported in this file, verify they are properly imported or aliased to keep type checking consistent.
</issue_to_address>

### Comment 2
<location> `evaluator/logic.py:510-519` </location>
<code_context>
                 to_delete,
             )

+    def _get_module_from_system(self, package_name: str, vmaas_json: dict) -> Tuple[Optional[str], Optional[str]]:
+        """Try to match package to module from system's modules_list"""
+
+        if not vmaas_json or not vmaas_json.get("modules_list"):
+            return None, None
+
+        # Check if package name matches any module name in the system's modules_list
+        for module in vmaas_json["modules_list"]:
+            if isinstance(module, dict):
+                module_name = module.get("module_name")
+                module_stream = module.get("module_stream")
+                # if package name starts with module name, likely from that module
+                if module_name and package_name.startswith(module_name):
+                    return module_name, module_stream
+        return None, None
</code_context>

<issue_to_address>
**issue:** Prefix-based module detection may mis-assign modules for similarly named packages.

Using `package_name.startswith(module_name)` risks matching unrelated modules that share a prefix (e.g., module `foo` vs package `foo-bar`). If this mapping affects correctness (e.g., stream selection or CVE attribution), consider tightening the condition (exact match, valid separators, or additional metadata) and/or restricting it to known-safe naming patterns in your environment.
</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 +310 to +312
async def _perform_vmaas_request(
self, vmaas_json: dict
) -> (List[CveAdvisories], List[CveAdvisories], List[CveUnpatched], List[CveUnpatched], List[CveUnpatched]):
Copy link

Choose a reason for hiding this comment

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

suggestion: Use a proper Tuple type for the multi-value return annotation instead of a bare parenthesized type list.

-> (List[...], ...) is parsed as just the last type, not a multi-value return. Please use a proper tuple annotation, e.g. -> Tuple[List[CveAdvisories], List[CveAdvisories], List[CveUnpatched], List[CveUnpatched], List[CveUnpatched]], so type checkers and editors understand the structure and can catch regressions if the return shape changes.

Suggested implementation:

    @time(EVAL_PART_TIME.labels(part="vmaas_request"))
    async def _perform_vmaas_request(
        self, vmaas_json: dict
    ) -> Tuple[
        List[CveAdvisories],
        List[CveAdvisories],
        List[CveUnpatched],
        List[CveUnpatched],
        List[CveUnpatched],
    ]:
  1. Ensure Tuple is imported from typing at the top of evaluator/logic.py, e.g. extend an existing from typing import ... line to include Tuple.
  2. If List and the CVE types are not already imported in this file, verify they are properly imported or aliased to keep type checking consistent.

Comment on lines +510 to +519
def _get_module_from_system(self, package_name: str, vmaas_json: dict) -> Tuple[Optional[str], Optional[str]]:
"""Try to match package to module from system's modules_list"""

if not vmaas_json or not vmaas_json.get("modules_list"):
return None, None

# Check if package name matches any module name in the system's modules_list
for module in vmaas_json["modules_list"]:
if isinstance(module, dict):
module_name = module.get("module_name")
Copy link

Choose a reason for hiding this comment

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

issue: Prefix-based module detection may mis-assign modules for similarly named packages.

Using package_name.startswith(module_name) risks matching unrelated modules that share a prefix (e.g., module foo vs package foo-bar). If this mapping affects correctness (e.g., stream selection or CVE attribution), consider tightening the condition (exact match, valid separators, or additional metadata) and/or restricting it to known-safe naming patterns in your environment.

@mtclinton mtclinton force-pushed the nodesjs-false-positive-fix branch from ef8b5e9 to 9a65cc4 Compare January 28, 2026 00:24
@mtclinton
Copy link
Contributor Author

/retest

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant