Skip to content

Expose raw CPE version ranges for advisory tooling - #408

Merged
laffer1 merged 3 commits into
masterfrom
feature/cpe-ranges
Aug 6, 2026
Merged

Expose raw CPE version ranges for advisory tooling#408
laffer1 merged 3 commits into
masterfrom
feature/cpe-ranges

Conversation

@laffer1

@laffer1 laffer1 commented Aug 5, 2026

Copy link
Copy Markdown
Member

What changed

  • add GET /api/cpe/ranges
  • return advisory metadata together with raw NVD CPE match constraints
  • preserve configuration node hierarchy, operators, negation, parent IDs, and inclusive/exclusive version boundaries
  • add controller and service coverage

Why

The existing version-matching endpoint reduces advisory data to a yes/no match. MidnightBSD's VuXML generator needs the original NVD boundaries so it can translate them into ranges using MidnightBSD package versions, revisions, and epochs instead of assuming FreeBSD package versions match.

Validation

  • full Maven test suite: 101 tests, 0 failures
  • focused controller and advisory service tests
  • git diff --check

Tests were run with Java 21 in interpreted mode on MidnightBSD. JaCoCo was skipped because its agent triggers a host JVM JIT crash. Spotless was not run because the repository's existing google-java-format version is incompatible with the available JDK compiler APIs.

Summary by Sourcery

Add an API endpoint to expose raw NVD CPE configuration ranges for advisories and wire it through the advisory service and controller.

New Features:

  • Introduce CpeRangeAdvisoryDto, CpeConfigurationDto, and CpeRangeDto to represent advisories with associated NVD CPE configuration and version range metadata.
  • Expose GET /api/cpe/ranges to return advisories and their CPE configuration hierarchies for a given CPE identifier.

Enhancements:

  • Extend AdvisoryService to build and return CPE range advisory DTOs while preserving configuration hierarchy and version boundary information.

Tests:

  • Add service- and controller-level tests to verify CPE range DTO construction and the /api/cpe/ranges endpoint response.

AI-Assisted-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Lucas Holt <luke@foolishgames.com>
@laffer1

laffer1 commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@sourcery-ai

sourcery-ai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new GET /api/cpe/ranges endpoint and corresponding service-layer DTO pipeline to expose raw NVD CPE configuration ranges (including hierarchy and version boundaries) for a given CPE, plus tests to validate parsing, filtering, and serialization behavior.

Sequence diagram for GET /api/cpe/ranges CPE range retrieval

sequenceDiagram
  title GET /api/cpe/ranges flow from controller to service
  actor Tool
  participant CpeController
  participant AdvisoryService

  Tool->>CpeController: ranges(cpe, startDate)
  CpeController->>CpeController: parse(cpe)
  CpeController->>AdvisoryService: cpeRangeDtos(vendor, product, startDate)
  AdvisoryService->>AdvisoryService: cpeRangeDto(advisory, vendor, product)
  AdvisoryService->>AdvisoryService: matchesProduct(criteria, vendor, product)
  AdvisoryService-->>CpeController: List<CpeRangeAdvisoryDto>
  CpeController-->>Tool: ResponseEntity<List<CpeRangeAdvisoryDto>>
Loading

File-Level Changes

Change Details Files
Expose a new advisory DTO model for raw CPE configuration ranges and wire it through AdvisoryService and CpeController.
  • Introduce CpeRangeAdvisoryDto to represent advisory metadata plus associated CPE configuration nodes.
  • Introduce CpeConfigurationDto to capture configuration node structure (id, parentId, operator, negate, matches).
  • Introduce CpeRangeDto to expose individual ConfigNodeCpe fields including raw criteria and all version boundary attributes.
  • Add AdvisoryService.cpeRangeDtos and helper methods to filter advisories by vendor/product, map config nodes into DTOs, sort nodes and matches deterministically, and guard against missing/invalid CPE data.
  • Add product-matching helper that parses CPE23 URIs, compares vendor/product case-insensitively, and logs warnings on parse failures.
src/main/java/org/midnightbsd/advisory/services/AdvisoryService.java
src/main/java/org/midnightbsd/advisory/dto/CpeRangeAdvisoryDto.java
src/main/java/org/midnightbsd/advisory/dto/CpeConfigurationDto.java
src/main/java/org/midnightbsd/advisory/dto/CpeRangeDto.java
Add the GET /api/cpe/ranges controller endpoint that parses a CPE string, validates input, and returns the new range DTOs.
  • Add CpeController.ranges() mapped to GET /api/cpe/ranges, accepting cpe and optional startDate query parameters.
  • Trim and validate the cpe parameter, returning HTTP 400 for blank input or CpeParsingException.
  • Parse the CPE into vendor/product and delegate to AdvisoryService.cpeRangeDtos, returning the results as JSON.
src/main/java/org/midnightbsd/advisory/ctl/api/CpeController.java
Extend unit and MVC tests to cover the new service behavior and ranges endpoint response shape.
  • Add AdvisoryServiceTest.cpeRangeDtosPreservesNvdRangeConstraints to verify configuration hierarchy, operators, and version boundaries are preserved in the DTOs.
  • Add CpeControllerTest.mvcTestGetCpeRanges to validate the /api/cpe/ranges endpoint returns 200 OK and JSON for a valid CPE.
  • Mock repositories and services in tests to control advisory/config-node setup and assert the new DTO fields are populated as expected.
src/test/java/org/midnightbsd/advisory/services/AdvisoryServiceTest.java
src/test/java/org/midnightbsd/advisory/ctl/CpeControllerTest.java

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

@laffer1
laffer1 marked this pull request as ready for review August 5, 2026 19:51
Copilot AI lite review requested due to automatic review settings August 5, 2026 19:51

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

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

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/test/java/org/midnightbsd/advisory/services/AdvisoryServiceTest.java" line_range="245-254" />
<code_context>
                 .andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"));
     }

+    @Test
+    void mvcTestGetCpeRanges() throws Exception {
+        when(advisoryService.cpeRangeDtos(anyString(), anyString(), ArgumentMatchers.isNull()))
</code_context>
<issue_to_address>
**issue (testing):** Add tests for edge cases in cpeRangeDtos (no config nodes, non-matching product, unparsable CPE URIs, and empty configurations).

Please add tests to cover the unhandled edge cases in `cpeRangeDtos`/`matchesProduct`:

- Advisory with `getConfigNodes() == null` returns an empty list and is skipped.
- Config nodes whose CPEs don’t match the requested vendor/product are filtered out (advisory effectively skipped).
- `ConfigNodeCpe` with malformed `cpe23Uri` (throwing in `CpeParser.parse`) is ignored without failing the response, and other nodes are still returned.
- Advisory whose `configurations` list ends up empty causes `cpeRangeDto` to return `null` and be omitted.

Targeted tests that set up these conditions (e.g., manipulating `adv.getConfigNodes()` and CPE URIs) will help ensure these behaviors remain stable across refactors.
</issue_to_address>

### Comment 2
<location path="src/test/java/org/midnightbsd/advisory/ctl/CpeControllerTest.java" line_range="98-95" />
<code_context>
                 .andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"));
     }

+    @Test
+    void mvcTestGetCpeRanges() throws Exception {
+        when(advisoryService.cpeRangeDtos(anyString(), anyString(), ArgumentMatchers.isNull()))
+                .thenReturn(List.of(new CpeRangeAdvisoryDto(
+                        1, TEST_CVE_ID, "TEST ARCH", null, null, "HIGH", null, List.of())));
+
+        mockMvc
+                .perform(get("/api/cpe/ranges?cpe=cpe:2.3:a:eric_allman:sendmail:5.58:*:*:*:*:*:*:*"))
+                .andExpect(status().isOk())
+                .andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"));
+    }
+
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen cpe ranges controller tests by asserting payload and service interaction.

Right now `mvcTestGetCpeRanges` only checks status and content type. Since this endpoint returns structured NVD range data, the test should also:

- Assert the JSON body shape and key fields (e.g. exactly one advisory; expected `cveId`, `severity`, `configurations` via `jsonPath`).
- Verify `advisoryService.cpeRangeDtos` is invoked with the vendor and product parsed from the CPE string (using `ArgumentCaptor` or stricter `when`/`verify` matchers), rather than generic `anyString()` arguments.

This will ensure the controller correctly maps DTOs and passes parsed CPE values into the service.

Suggested implementation:

```java
import org.midnightbsd.advisory.services.AdvisoryService;
import org.mockito.ArgumentMatchers;

import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

```

```java
    @Test
    void mvcTestGetCpeRanges() throws Exception {
        when(advisoryService.cpeRangeDtos(
                        ArgumentMatchers.eq("eric_allman"),
                        ArgumentMatchers.eq("sendmail"),
                        ArgumentMatchers.isNull()))
                .thenReturn(List.of(new CpeRangeAdvisoryDto(
                        1, TEST_CVE_ID, "TEST ARCH", null, null, "HIGH", null, List.of())));

        mockMvc
                .perform(get("/api/cpe/ranges?cpe=cpe:2.3:a:eric_allman:sendmail:5.58:*:*:*:*:*:*:*"))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"))
                .andExpect(jsonPath("$.length()").value(1))
                .andExpect(jsonPath("$[0].cveId").value(TEST_CVE_ID))
                .andExpect(jsonPath("$[0].severity").value("HIGH"))
                .andExpect(jsonPath("$[0].configurations").isArray());

        verify(advisoryService)
                .cpeRangeDtos(
                        ArgumentMatchers.eq("eric_allman"),
                        ArgumentMatchers.eq("sendmail"),
                        ArgumentMatchers.isNull());

```

If the JSON field names in the CPE ranges response differ from `cveId`, `severity`, or `configurations`, adjust the `jsonPath` expressions to match the actual serialized property names used in `CpeRangeAdvisoryDto`.
</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 +245 to +254
@Test
void cpeRangeDtosPreservesNvdRangeConstraints() {
var vendor = new Vendor();
vendor.setName("vendor");
var product = new Product();
product.setName("product");
product.setVendor(vendor);
ConfigNodeCpe cpe = adv.getConfigNodes().iterator().next().getConfigNodeCpes().iterator().next();
cpe.setVersionStartIncluding("1.0");
cpe.setVersionEndExcluding("2.0");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (testing): Add tests for edge cases in cpeRangeDtos (no config nodes, non-matching product, unparsable CPE URIs, and empty configurations).

Please add tests to cover the unhandled edge cases in cpeRangeDtos/matchesProduct:

  • Advisory with getConfigNodes() == null returns an empty list and is skipped.
  • Config nodes whose CPEs don’t match the requested vendor/product are filtered out (advisory effectively skipped).
  • ConfigNodeCpe with malformed cpe23Uri (throwing in CpeParser.parse) is ignored without failing the response, and other nodes are still returned.
  • Advisory whose configurations list ends up empty causes cpeRangeDto to return null and be omitted.

Targeted tests that set up these conditions (e.g., manipulating adv.getConfigNodes() and CPE URIs) will help ensure these behaviors remain stable across refactors.

@@ -94,6 +95,18 @@ void mvcTestGetCpeWithIncludeVersion() throws Exception {
.andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Strengthen cpe ranges controller tests by asserting payload and service interaction.

Right now mvcTestGetCpeRanges only checks status and content type. Since this endpoint returns structured NVD range data, the test should also:

  • Assert the JSON body shape and key fields (e.g. exactly one advisory; expected cveId, severity, configurations via jsonPath).
  • Verify advisoryService.cpeRangeDtos is invoked with the vendor and product parsed from the CPE string (using ArgumentCaptor or stricter when/verify matchers), rather than generic anyString() arguments.

This will ensure the controller correctly maps DTOs and passes parsed CPE values into the service.

Suggested implementation:

import org.midnightbsd.advisory.services.AdvisoryService;
import org.mockito.ArgumentMatchers;

import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
    @Test
    void mvcTestGetCpeRanges() throws Exception {
        when(advisoryService.cpeRangeDtos(
                        ArgumentMatchers.eq("eric_allman"),
                        ArgumentMatchers.eq("sendmail"),
                        ArgumentMatchers.isNull()))
                .thenReturn(List.of(new CpeRangeAdvisoryDto(
                        1, TEST_CVE_ID, "TEST ARCH", null, null, "HIGH", null, List.of())));

        mockMvc
                .perform(get("/api/cpe/ranges?cpe=cpe:2.3:a:eric_allman:sendmail:5.58:*:*:*:*:*:*:*"))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"))
                .andExpect(jsonPath("$.length()").value(1))
                .andExpect(jsonPath("$[0].cveId").value(TEST_CVE_ID))
                .andExpect(jsonPath("$[0].severity").value("HIGH"))
                .andExpect(jsonPath("$[0].configurations").isArray());

        verify(advisoryService)
                .cpeRangeDtos(
                        ArgumentMatchers.eq("eric_allman"),
                        ArgumentMatchers.eq("sendmail"),
                        ArgumentMatchers.isNull());

If the JSON field names in the CPE ranges response differ from cveId, severity, or configurations, adjust the jsonPath expressions to match the actual serialized property names used in CpeRangeAdvisoryDto.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new API surface to return advisories along with the raw NVD CPE configuration/range constraints (including configuration node hierarchy and inclusive/exclusive version bounds) so downstream tooling can perform its own version-range translation.

Changes:

  • Add GET /api/cpe/ranges to return advisory metadata plus raw configuration/match constraints.
  • Extend AdvisoryService to build range-focused DTOs that preserve config node structure and version boundaries.
  • Add service/controller tests covering the new ranges behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/main/java/org/midnightbsd/advisory/ctl/api/CpeController.java Adds /api/cpe/ranges endpoint with CPE parsing/validation and optional startDate.
src/main/java/org/midnightbsd/advisory/services/AdvisoryService.java Adds cpeRangeDtos() and DTO construction preserving config node structure and match boundaries.
src/main/java/org/midnightbsd/advisory/dto/CpeRangeDto.java New DTO representing raw NVD CPE match constraints including inclusive/exclusive bounds.
src/main/java/org/midnightbsd/advisory/dto/CpeConfigurationDto.java New DTO representing a configuration node and its match list.
src/main/java/org/midnightbsd/advisory/dto/CpeRangeAdvisoryDto.java New top-level DTO bundling advisory metadata with configuration nodes.
src/test/java/org/midnightbsd/advisory/ctl/CpeControllerTest.java Adds MVC coverage for the new /api/cpe/ranges endpoint.
src/test/java/org/midnightbsd/advisory/services/AdvisoryServiceTest.java Adds unit coverage asserting range bounds and config-node hierarchy are preserved.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 6, 2026 13:00
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@laffer1
laffer1 merged commit 022bdbd into master Aug 6, 2026
6 of 9 checks passed
@laffer1
laffer1 deleted the feature/cpe-ranges branch August 6, 2026 13:01
@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

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.

2 participants