Skip to content

Fix CVE details page 400 error by respecting no-tags patterns#1164

Open
nofaralfasi wants to merge 1 commit intotheforeman:developfrom
nofaralfasi:SAT_42738
Open

Fix CVE details page 400 error by respecting no-tags patterns#1164
nofaralfasi wants to merge 1 commit intotheforeman:developfrom
nofaralfasi:SAT_42738

Conversation

@nofaralfasi
Copy link
Collaborator

@nofaralfasi nofaralfasi commented Mar 3, 2026

The scope_request? method was incorrectly filtering to only consider patterns with tag_name defined, which caused the CVE details endpoint pattern (api/vulnerability/v1/cves/[^/]+$) to be excluded.

This resulted in the more general pattern (api/vulnerability/v1/.*) being selected instead, which adds tags to the request. Since the CVE details API endpoint does not support tags per the OpenAPI spec, this caused a 400 Bad Request error:
"Extra query parameter(s) tags not in spec"

What are the changes introduced in this pull request?

The fix removes the tag_name filter so all matching patterns are considered, then the most specific pattern (longest regex) is selected. For the CVE details endpoint, this correctly returns nil for tag_name, meaning no tags are added to the request.

Fixes the "Cannot read properties of undefined (reading 'synopsis')" error that occurred when clicking on any CVE from the Vulnerabilities page or host details Vulnerabilities tab.

What are the testing steps for this pull request?

Scenario 1:
Navigation Path:
Insights → Vulnerability → Click on any CVE

Scenario 2:
Navigation Path:
Hosts → All Hosts → client.example.com → Vulnerabilities → Click on any CVE

Expected Result:
Clicking on any CVE should open the Vulnerability details page and display full CVE information, including synopsis, description, severity, affected packages, etc.

Summary by Sourcery

Bug Fixes:

  • Fix 400 errors and frontend failures on CVE detail pages by correctly handling tag-less vulnerability CVE endpoints in scoped request matching.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 3, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adjusts scoped request pattern selection for Insights API forwarding so that GET requests consider both tagged and non-tagged patterns and pick the most specific regex, fixing CVE detail requests that should not have tags applied.

Sequence diagram for CVE details scoped request pattern selection

sequenceDiagram
    actor User
    participant UI as Vulnerability_UI
    participant Server as Foreman_Server
    participant Forwarder as InsightsApiForwarder
    participant Insights as Insights_API

    User->>UI: Click CVE in Vulnerabilities view
    UI->>Server: GET /api/vulnerability/v1/cves/CVE-2024-1234
    Server->>Forwarder: forward_request(original_request, path)

    Forwarder->>Forwarder: scope_request?(original_request, path)
    Note over Forwarder: original_request.get? == true

    Forwarder->>Forwarder: Find matching_patterns where
    Forwarder->>Forwarder: pattern.test.match?(path)
    Forwarder->>Forwarder: and (pattern.tag_name OR permissions[GET])

    Forwarder->>Forwarder: Evaluate CVE_detail_pattern
    Note over Forwarder: test: /api\/vulnerability\/v1\/cves\/[^/]+$/
    Note over Forwarder: tag_name: nil
    Note over Forwarder: permissions: { GET: true }

    Forwarder->>Forwarder: Evaluate general_pattern
    Note over Forwarder: test: /api\/vulnerability\/v1\/.*
    Note over Forwarder: tag_name: tags
    Note over Forwarder: permissions: { GET: true }

    Forwarder->>Forwarder: Select most specific pattern by
    Forwarder->>Forwarder: max [regex_length, tag_support_flag]
    Note over Forwarder: CVE_detail_pattern chosen

    Forwarder-->>Forwarder: tag_name = nil

    alt tag_name is nil
        Forwarder->>Insights: GET /api/vulnerability/v1/cves/CVE-2024-1234
    else tag_name present (previous behavior)
        Forwarder->>Insights: GET /api/vulnerability/v1/cves/CVE-2024-1234?tags=...
    end

    Insights-->>Forwarder: 200 OK, CVE details JSON
    Forwarder-->>Server: CVE details JSON
    Server-->>UI: CVE details JSON
    UI-->>User: Render CVE synopsis, description, severity, packages
Loading

File-Level Changes

Change Details Files
Refine scoped request pattern filtering and selection logic for GET requests to correctly handle non-tagged CVE detail routes and avoid adding unsupported tags.
  • Change matching pattern selection to include any GET-relevant pattern that either has a tag_name or defines GET permissions, instead of only patterns with tag_name
  • Filter SCOPED_REQUESTS by both path regex match and GET relevance, ignoring patterns that don’t apply to GET requests
  • Update most-specific-pattern selection to use regex length and then prefer tag-supporting patterns as a tie-breaker to avoid order-dependent behavior
  • Return tag_name from the selected pattern, which may be nil when the most specific matching pattern does not support tags
app/services/foreman_rh_cloud/insights_api_forwarder.rb

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 1 issue, and left some high level feedback:

  • Using regex source length as a proxy for specificity may produce surprising precedence in some edge cases; consider making precedence explicit (e.g., with a priority field) instead of relying on pattern length.
  • Now that patterns without tag_name can override those with tag_name, it may be worth validating that any permission-only patterns in SCOPED_REQUESTS are still behaving as intended and are not accidentally suppressing tags for endpoints where they should be added.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using regex source length as a proxy for specificity may produce surprising precedence in some edge cases; consider making precedence explicit (e.g., with a priority field) instead of relying on pattern length.
- Now that patterns without `tag_name` can override those with `tag_name`, it may be worth validating that any permission-only patterns in `SCOPED_REQUESTS` are still behaving as intended and are not accidentally suppressing tags for endpoints where they should be added.

## Individual Comments

### Comment 1
<location path="app/services/foreman_rh_cloud/insights_api_forwarder.rb" line_range="231-234" />
<code_context>

-      # Choose the most specific pattern by regex source length for consistency
-      # with required_permission_for behavior
+      # Choose the most specific pattern by regex source length
+      # This ensures patterns explicitly defined without tag_name take precedence
+      # over general patterns that do have tag_name
       request_pattern = matching_patterns.max_by { |pattern| pattern[:test].source.length }
+
+      # Return the tag_name (may be nil if the most specific pattern doesn't support tags)
</code_context>
<issue_to_address>
**suggestion:** Consider tie-breaking to prefer tag-supporting patterns when specificity is equal

Because both permission-only and tag-supporting patterns now participate in `max_by`, ties on `regex.source.length` are currently broken only by definition order in `SCOPED_REQUESTS`. To avoid this order dependency, consider using a composite key like `[pattern[:test].source.length, pattern[:tag_name] ? 1 : 0]` (or the inverse, depending on desired behavior) so that tie-breaking between tag-supporting and non-tag patterns is explicit and deterministic.
</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.

Copy link
Collaborator

@jeremylenz jeremylenz left a comment

Choose a reason for hiding this comment

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

I tested this and it fixes the issue.

Will leave it to @vkrizan to see if he agrees with bot's comments here. also the test failure seems related :)

@nofaralfasi
Copy link
Collaborator Author

@sourcery-ai review

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 left some high level feedback:

  • The new pattern.dig(:permissions, 'GET') check assumes permission keys are strings; if any entries use symbol keys (e.g. :GET), this will silently skip them—consider normalizing or supporting both.
  • Since both scope_request? and required_permission_for now rely on similar pattern-selection semantics (specificity, permissions), consider extracting the matching/selection logic into a shared helper to avoid divergence over time.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `pattern.dig(:permissions, 'GET')` check assumes permission keys are strings; if any entries use symbol keys (e.g. `:GET`), this will silently skip them—consider normalizing or supporting both.
- Since both `scope_request?` and `required_permission_for` now rely on similar pattern-selection semantics (specificity, permissions), consider extracting the matching/selection logic into a shared helper to avoid divergence over time.

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.

@jeremylenz
Copy link
Collaborator

  • The new pattern.dig(:permissions, 'GET') check assumes permission keys are strings; if any entries use symbol keys (e.g. :GET), this will silently skip them—consider normalizing or supporting both.

If .with_indifferent_access is called on these at any point, we can use either string or hash keys and not worry about it.

Copy link
Contributor

@vkrizan vkrizan left a comment

Choose a reason for hiding this comment

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

Thanks for this fix. I've left some comments for possible simplification of the matching criteria.

# This ensures patterns like api/vulnerability/v1/cves/[^/]+$ (GET without tags)
# take precedence over general patterns, while POST-only patterns are ignored.
matching_patterns = SCOPED_REQUESTS.select do |pattern|
pattern[:test].match?(path) && (pattern[:tag_name] || pattern.dig(:permissions, 'GET'))
Copy link
Contributor

Choose a reason for hiding this comment

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

I see that this combines two unrelated things: 1) that it needs to have a tag definition; or 2) it needs to have a GET permissions.

Could you elaborate on the requirement for testing the existence of a tag definition? Could there be just one requirement for GET permissions definition? Or is this kept for a backwards compatibility?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Let me explain why I beleieve both conditions are needed.
Looking at the SCOPED_REQUESTS patterns, there are different categories:
Category A: Patterns with tag_name but NO permissions defined:
These patterns exist purely for tagging - they don't enforce permissions but do add tags to GET requests. Without the tag_name check, these would be excluded.

Category B: Patterns with GET permissions but NO tag_name:
These patterns explicitly say "this GET endpoint does NOT support tags". Without the GET permissions check, these would be excluded (the bug we're fixing).

If we only used pattern.dig(:permissions, 'GET'):

  • api/inventory/.* and api/tasks/.* would be excluded (breaking change)
  • GET requests to those endpoints would no longer get tags

So yes, it combines two concepts, but both are required to:

  • Fix the CVE details bug (needs GET check)
  • Maintain backwards compatibility for tagging-only patterns (needs tag_name check)

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the explanation!

# Tie-break by preferring tag-supporting patterns when specificity is equal,
# to avoid order-dependent behavior in SCOPED_REQUESTS.
request_pattern = matching_patterns.max_by do |pattern|
[pattern[:test].source.length, pattern[:tag_name] ? 1 : 0]
Copy link
Contributor

Choose a reason for hiding this comment

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

This matching condition for preferring matches by tag definition could be simplified just to the rule of longest match on path. Could you elaborate on the need for that tag preference over simplification?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right - the tie-breaking logic for preferring tag-supporting patterns adds unnecessary complexity.
The simple "longest match wins" rule is sufficient.

@nofaralfasi
Copy link
Collaborator Author

  • The new pattern.dig(:permissions, 'GET') check assumes permission keys are strings; if any entries use symbol keys (e.g. :GET), this will silently skip them—consider normalizing or supporting both.

If .with_indifferent_access is called on these at any point, we can use either string or hash keys and not worry about it.

I checked and all permission entries in SCOPED_REQUESTS currently use string keys ('GET', 'POST', etc.), so pattern.dig(:permissions, 'GET') should be safe here.
This is also consistent with how required_permission_for accesses permissions using string keys.
That said, if you think it's worth adding a safeguard for future-proofing (like supporting both or adding a comment), I'm happy to do that.

The scope_request? method was incorrectly filtering to only consider
patterns with tag_name defined, which caused the CVE details endpoint
pattern (api/vulnerability/v1/cves/[^/]+$) to be excluded.

This resulted in the more general pattern (api/vulnerability/v1/.*)
being selected instead, which adds tags to the request. Since the
CVE details API endpoint does not support tags per the OpenAPI spec,
this caused a 400 Bad Request error:
"Extra query parameter(s) tags not in spec"

The fix removes the tag_name filter so all matching patterns are
considered, then the most specific pattern (longest regex) is selected.
For the CVE details endpoint, this correctly returns nil for tag_name,
meaning no tags are added to the request.

Fixes the "Cannot read properties of undefined (reading 'synopsis')"
error that occurred when clicking on any CVE from the Vulnerabilities
page or host details Vulnerabilities tab.
@jeremylenz
Copy link
Collaborator

That said, if you think it's worth adding a safeguard for future-proofing (like supporting both or adding a comment), I'm happy to do that.

If it works as-is I'm fine without it.

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.

3 participants