Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ curl -X POST "http://localhost:3000/search" \

Version ranges use overlap semantics. A stored wildcard-version CPE Match Criteria entry such as `cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*` with `versionEndExcluding=6.9` matches requested ranges that overlap versions before `6.9`, and does not match a requested range that starts at `6.9`. Stored intervals with a start after their end, or equal boundaries where either boundary is exclusive, are empty and do not match range searches.

CPE version comparisons used by both `cpeName` applicability and `virtualMatchString` version ranges use Maven-style qualifier precedence: `alpha`/`a` < `beta`/`b` < `milestone`/`m` < `rc`/`cr` < `snapshot` < an unqualified release/`ga`/`final`/`release` < `sp` < unknown qualifiers. Unknown qualifiers, including `hf`, sort after known qualifiers in case-insensitive lexical order. This means a range beginning at `1.0.0-rc1` includes `1.0.0`, while a range ending before `1.0.0` can include prerelease versions.
CPE version comparisons used by both `cpeName` applicability and `virtualMatchString` version ranges use an API-defined extension of Maven-style qualifier precedence: `dev` < `alpha` < `beta` < `milestone` < `pre` < `preview` < `rc`/`cr` < `snapshot` < an unqualified release/`ga`/`final`/`release` < `sp` < unknown qualifiers. The short qualifiers `a`, `b`, and `m` are aliases for `alpha`, `beta`, and `milestone` only when immediately followed by a digit, such as `2.0a1`, `1.0b2`, or `1.0-m1`. Bare trailing letters remain literal qualifiers after the release, so `1.0.2 < 1.0.2a < 1.0.2b` and `1.1.1l < 1.1.1m`. A separator between the letter and number, as in `1.0a.1`, does not activate the short alias. The stages `pre`, `preview`, and `rc` are distinct, not aliases: `2.0.0-pre1 < 2.0.0-preview1 < 2.0.0-rc1 < 2.0.0`. Development versions precede alpha versions, so `2.0.0-dev1 < 2.0.0-alpha1`. These additional stages are an API policy rather than strict Maven behavior. Unknown qualifiers, including `hf`, sort after known qualifiers in case-insensitive lexical order. This means a range beginning at `1.0.0-rc1` includes `1.0.0`, while a range ending before `1.0.0` can include prerelease versions. Qualifier normalization applies to range comparisons; exact criteria versions remain literal matches.

Numeric segments used in version range comparisons ignore leading zeros. A stored exclusive upper bound of `10.05.1` therefore includes `10.5.0`, and CVE-2026-33280's exclusive `6.02` upper bound includes `6.1` but excludes `6.2`. This normalization applies only to range comparison. Exact CPE Match Criteria preserve leading zeros, so the CVE-2025-9392 versions `1.0.04.001` and `1.0.4.1` are not equivalent. This comparison rule is not a universal firmware-versioning rule and can differ from a vendor's actual release sequence.

Expand Down
6 changes: 3 additions & 3 deletions api-docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@
"type": "string",
"maxLength": 2048,
"example": "cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*",
"description": "Returns CVE records whose CPE Match Criteria apply to the provided CPE 2.3 asset name. The value must begin with cpe:2.3 and contain concrete part, vendor, product, and version components; part must be a, h, or o. Unescaped * or ? characters anywhere in those required components, including versions such as 2.6.*, return HTTP 400. Backslash-escaped literal characters remain allowed. Empty components, malformed escaping, and unsupported part values return HTTP 400. CPE string literal comparisons are insensitive to lexical case. Every concrete non-version component must be compatible with the same CPE Match Criteria entry that satisfies the version and vulnerability checks; optional * components remain broad matches. The response includes a CPE_COMPONENTS_DEFAULTED warning when omitted trailing components are treated as wildcards. The concrete version can match stored criteria with the same version or criteria with a wildcard version and version range fields. The requested version need not appear literally in a record when it falls within a range. The API does not verify that a software version actually exists; open-ended criteria can match hypothetical versions. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. The version - represents the CPE logical value NA, not an unknown or unrestricted version; numeric range boundaries on wildcard-version criteria do not exclude it under the API matching policy. Use virtualMatchString for partial CPE Match String searches or all versions of a product. For a version interval, omit the virtualMatchString version component and supply versionStart/versionStartType and/or versionEnd/versionEndType. A cpeName version ending in punctuation, such as 2.6., is not a version-prefix search."
"description": "Returns CVE records whose CPE Match Criteria apply to the provided CPE 2.3 asset name. The value must begin with cpe:2.3 and contain concrete part, vendor, product, and version components; part must be a, h, or o. Unescaped * or ? characters anywhere in those required components, including versions such as 2.6.*, return HTTP 400. Backslash-escaped literal characters remain allowed. Empty components, malformed escaping, and unsupported part values return HTTP 400. CPE string literal comparisons are insensitive to lexical case. Every concrete non-version component must be compatible with the same CPE Match Criteria entry that satisfies the version and vulnerability checks; optional * components remain broad matches. The response includes a CPE_COMPONENTS_DEFAULTED warning when omitted trailing components are treated as wildcards. The concrete version can match stored criteria with the same version or criteria with a wildcard version and version range fields. The requested version need not appear literally in a record when it falls within a range. The API does not verify that a software version actually exists; open-ended criteria can match hypothetical versions. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering extended by the API policy, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. The short qualifiers a, b, and m expand only when immediately followed by a digit, such as 2.0a1. Bare trailing letters sort after the release in case-insensitive lexical order, so 1.0.2 precedes 1.0.2a and 1.1.1l precedes 1.1.1m. A separator between a short letter and its number does not activate the alias. The prerelease order is dev, alpha, beta, milestone, pre, preview, rc, snapshot. The labels pre, preview, and rc are distinct, not aliases. The version - represents the CPE logical value NA, not an unknown or unrestricted version; numeric range boundaries on wildcard-version criteria do not exclude it under the API matching policy. Use virtualMatchString for partial CPE Match String searches or all versions of a product. For a version interval, omit the virtualMatchString version component and supply versionStart/versionStartType and/or versionEnd/versionEndType. A cpeName version ending in punctuation, such as 2.6., is not a version-prefix search."
},
"isVulnerable": {
"oneOf": [
Expand Down Expand Up @@ -853,7 +853,7 @@
"type": "string",
"maxLength": 256,
"example": "2.2",
"description": "Sets the starting version bound for a CPE Match String range search. versionStartType and virtualMatchString are required when versionStart is provided. Negative numeric values and the standalone CPE logical NA value (-) are not accepted as ordered range boundaries. When both bounds are provided, versionStart must be less than versionEnd; equal values require both bounds to be including. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. virtualMatchString must not include a version component."
"description": "Sets the starting version bound for a CPE Match String range search. versionStartType and virtualMatchString are required when versionStart is provided. Negative numeric values and the standalone CPE logical NA value (-) are not accepted as ordered range boundaries. When both bounds are provided, versionStart must be less than versionEnd; equal values require both bounds to be including. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering extended by the API policy, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. The short qualifiers a, b, and m expand only when immediately followed by a digit, such as 2.0a1. Bare trailing letters sort after the release in case-insensitive lexical order, so 1.0.2 precedes 1.0.2a and 1.1.1l precedes 1.1.1m. A separator between a short letter and its number does not activate the alias. The prerelease order is dev, alpha, beta, milestone, pre, preview, rc, snapshot. The labels pre, preview, and rc are distinct, not aliases. virtualMatchString must not include a version component."
},
"versionStartType": {
"type": "string",
Expand All @@ -867,7 +867,7 @@
"type": "string",
"maxLength": 256,
"example": "2.6",
"description": "Sets the ending version bound for a CPE Match String range search. versionEndType and virtualMatchString are required when versionEnd is provided. Negative numeric values and the standalone CPE logical NA value (-) are not accepted as ordered range boundaries. When both bounds are provided, versionStart must be less than versionEnd; equal values require both bounds to be including. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. virtualMatchString must not include a version component."
"description": "Sets the ending version bound for a CPE Match String range search. versionEndType and virtualMatchString are required when versionEnd is provided. Negative numeric values and the standalone CPE logical NA value (-) are not accepted as ordered range boundaries. When both bounds are provided, versionStart must be less than versionEnd; equal values require both bounds to be including. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering extended by the API policy, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. The short qualifiers a, b, and m expand only when immediately followed by a digit, such as 2.0a1. Bare trailing letters sort after the release in case-insensitive lexical order, so 1.0.2 precedes 1.0.2a and 1.1.1l precedes 1.1.1m. A separator between a short letter and its number does not activate the alias. The prerelease order is dev, alpha, beta, milestone, pre, preview, rc, snapshot. The labels pre, preview, and rc are distinct, not aliases. virtualMatchString must not include a version component."
},
"versionEndType": {
"type": "string",
Expand Down
42 changes: 42 additions & 0 deletions routes/search.integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,48 @@ describe('search route integration', () => {
expect(mockSearchOpenSearch).not.toHaveBeenCalled();
});

test.each([
['1.0.2', '1.0.2a', 200],
['1.1.1l', '1.1.1m', 200],
['2.0a1', '2.0', 200],
['1.0b2', '1.0', 200],
['1.0m3', '1.0', 200],
['2.0.0-dev1', '2.0.0-alpha1', 200],
['2.0.0-pre1', '2.0.0-preview1', 200],
['2.0.0-preview1', '2.0.0-rc1', 200],
['2.0.0-preview1', '2.0.0', 200],
['1.0.2a', '1.0.2', 400],
['1.1.1m', '1.1.1l', 400],
['2.0', '2.0a1', 400],
['2.0.0', '2.0.0-dev1', 400],
['2.0.0-preview1', '2.0.0-pre1', 400],
['2.0.0-rc1', '2.0.0-preview1', 400]
])(
'validates qualifier and patch-letter ranges from %s to %s',
async (versionStart, versionEnd, expectedStatus) => {
mockSearchOpenSearch.mockResolvedValue(advancedResponse(0).data);
const response = await request(createApp(), {
method: 'POST',
path: '/search',
body: {
virtualMatchString: 'cpe:2.3:a:openssl:openssl',
versionStart,
versionStartType: 'including',
versionEnd,
versionEndType: 'including'
}
});
expect(response.status).toBe(expectedStatus);
if (expectedStatus === 200) {
expect(response.body.pagination.totalResults).toBe(0);
expect(mockSearchOpenSearch).toHaveBeenCalledTimes(1);
} else {
expect(response.body.error).toBe('VERSION_RANGE_ORDER');
expect(mockSearchOpenSearch).not.toHaveBeenCalled();
}
}
);

test('POST /search rejects an inverted virtualMatchString version range before invoking OpenSearch', async () => {
const response = await request(createApp(), {
method: 'POST',
Expand Down
Loading
Loading