Skip to content
Merged
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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ String inputs are bounded before query construction:
| `keywordSearch` | 512 characters and 64 whitespace-delimited terms |
| `cveIds` | 4,096 characters overall, 100 IDs, and 64 characters per ID |
| `cweId` | 32 characters |
| `sourceIdentifier` | 512 characters |
| `cpeName` | 2,048 characters |
| `virtualMatchString` | 2,048 characters |
| `versionStart`, `versionEnd` | 256 characters each |
Expand Down
11 changes: 2 additions & 9 deletions api-docs/openapi.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"openapi": "3.0.2",
"info": {
"version": "0.3.0-beta",
"version": "0.4.0-beta",
"title": "CVE Search API",
"description": "CVE Search API to retrieve all matching CVE records after applying request inputs as filters.",
"contact": {
Expand Down Expand Up @@ -108,7 +108,6 @@
"cvssV3_0Metrics": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"cvssV3_1Metrics": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"cvssV4Metrics": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"sourceIdentifier": "security@example.org",
"cpeName": "cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*",
"isVulnerable": false,
"lastModStartDate": "2024-01-01T00:00:00.000Z",
Expand Down Expand Up @@ -409,12 +408,6 @@
"example": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"description": "Returns CVE records that contain the provided CVSS v4.0 vector string. Values are limited to 256 characters, 32 slash-delimited metric tokens, and 32 characters per metric token."
},
"sourceIdentifier": {
"type": "string",
"maxLength": 512,
"example": "security@example.org",
"description": "Returns CVE records that contain the provided source identifier."
},
"cpeName": {
"type": "string",
"maxLength": 2048,
Expand Down Expand Up @@ -503,4 +496,4 @@
}
}
}
}
}
6 changes: 6 additions & 0 deletions api-docs/openapi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const fs = require('fs');
const os = require('os');
const path = require('path');
const openapi = require('./openapi.json');
const packageInfo = require('../package.json');
const constants = require('../utils/constants');
const { generateOpenApi } = require('../routes/swagger');

Expand Down Expand Up @@ -33,6 +34,10 @@ describe('OpenAPI contract', () => {
]);
});

test('uses the project package version', () => {
expect(openapi.info.version).toBe(packageInfo.version);
});

test('all path parameter references resolve to component parameters', () => {
Object.values(openapi.paths).forEach((pathItem) => {
Object.values(pathItem).forEach((operation) => {
Expand Down Expand Up @@ -83,6 +88,7 @@ describe('OpenAPI contract', () => {
expect(schema.properties.versionEndType.enum).toStrictEqual(constants.CPE_VERSION_RANGE_TYPES);
expect(schema.properties.resultsPerPage.maximum).toBe(constants.MAX_RESULTS_LIMIT);
expect(schema.properties.cveId).toBeUndefined();
expect(schema.properties.sourceIdentifier).toBeUndefined();
expect(schema.properties.versionStart.description).toContain('versionStartType');
expect(schema.properties.versionStart.description).toContain('less than versionEnd');
expect(schema.properties.versionStart.description).toContain('ranges ignore leading zeros');
Expand Down
13 changes: 7 additions & 6 deletions controllers/middleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,6 @@ describe('middleware validation function', () => {
['cvssV3_0Metrics', 'CVSS:3.0/AV:N/AC:L'],
['cvssV3_1Metrics', 'CVSS:3.1/PR:N/UI:R'],
['cvssV4Metrics', 'CVSS:4.0/AV:N/AC:L'],
['sourceIdentifier', 'CISA'],
['cpeName', 'cpe:2.3:a:vendor:product:1.0:*:*:*:*:*:*:*'],
['isVulnerable', 'false'],
['lastModStartDate', '2024-01-01T00:00:00.000Z'],
Expand All @@ -415,12 +414,15 @@ describe('middleware validation function', () => {
expectValidationSuccess();
});

test('ValidateParams returns an error when deprecated cveId is provided.', () => {
test.each([
['cveId', 'CVE-2024-0001'],
['sourceIdentifier', 'CISA']
])('ValidateParams returns an error when unsupported %s is provided.', (param, value) => {
validate({
cveId: 'CVE-2024-0001'
[param]: value
});

expectValidationError('INVALID_PARAM', 'cveId is not supported by the Search API.');
expectValidationError('INVALID_PARAM', `${param} is not supported by the Search API.`);
});

test.each([
Expand Down Expand Up @@ -461,8 +463,7 @@ describe('middleware validation function', () => {

test.each([
['keywordSearch', 'a'.repeat(constants.SEARCH_STRING_LENGTH_LIMITS.keywordSearch)],
['cweId', 'a'.repeat(constants.SEARCH_STRING_LENGTH_LIMITS.cweId)],
['sourceIdentifier', 'a'.repeat(constants.SEARCH_STRING_LENGTH_LIMITS.sourceIdentifier)]
['cweId', 'a'.repeat(constants.SEARCH_STRING_LENGTH_LIMITS.cweId)]
])('ValidateParams accepts %s at its maximum length.', (param, value) => {
validate({
[param]: value
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cve-search-api",
"version": "0.3.0-beta",
"version": "0.4.0-beta",
"scripts": {
"dev": "node routes/swagger.js && nodemon server.js",
"coverage": "jest --coverage",
Expand Down
1 change: 0 additions & 1 deletion routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ router.post(
cvssV3_0Metrics: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
cvssV3_1Metrics: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
cvssV4Metrics: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
sourceIdentifier: "security@example.org",
cpeName: "cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*",
isVulnerable: false,
lastModStartDate: "2024-01-01T00:00:00.000Z",
Expand Down
8 changes: 2 additions & 6 deletions routes/swagger.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const path = require('path');
const swaggerAutogen = require('swagger-autogen');
const packageInfo = require('../package.json');
const constants = require('../utils/constants');

const projectRoot = path.join(__dirname, '..');
Expand Down Expand Up @@ -121,11 +122,6 @@ const searchRequestProperties = {
example: 'CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'
}
),
sourceIdentifier: searchRequestProperty('Returns CVE records that contain the provided source identifier.', {
type: 'string',
maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.sourceIdentifier,
example: 'security@example.org'
}),
cpeName: searchRequestProperty(
'Returns CVE records whose CPE Match Criteria apply to the provided CPE 2.3 asset name. CPE string literal comparisons are insensitive to lexical case. The part, vendor, product, and version components must contain values other than *. 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 concrete version can match stored criteria with the same version or criteria with a wildcard version and version range fields. 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; numeric range boundaries on wildcard-version criteria do not exclude it. Invalid cpeName values return HTTP 404. Use virtualMatchString for partial CPE Match String searches.',
{
Expand Down Expand Up @@ -235,7 +231,7 @@ const searchRequestProperties = {
const doc = {
openapi: '3.0.2',
info: {
version: '0.3.0-beta',
version: packageInfo.version,
title: 'CVE Search API',
description: 'CVE Search API to retrieve all matching CVE records after applying request inputs as filters.',
contact: {
Expand Down
4 changes: 0 additions & 4 deletions utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const SEARCH_STRING_LENGTH_LIMITS = Object.freeze({
keywordSearch: 512,
cveIds: 4096,
cweId: 32,
sourceIdentifier: 512,
cpeName: 2048,
versionStart: 256,
versionEnd: 256,
Expand Down Expand Up @@ -56,7 +55,6 @@ const cpeMatchFields = {
* cvssV3_0Metrics: CVSSv3_0 vector string
* cvssV3_1Metrics: CVSSv3_1 vector string
* cvssV4Metrics: CVSSv4 vector string
* sourceIdentifier: string
* cpeName: string, name of CPE to search for
* isVulnerable: boolean
* versionStart: string, lower version bound used with virtualMatchString
Expand All @@ -79,7 +77,6 @@ const fields = {
cvssV3Metrics: ['containers.cna.metrics.cvssV3_0.vectorString', 'containers.cna.metrics.cvssV3_1.vectorString'],
cvssV4Severity: 'containers.cna.metrics.cvssV4_0.baseSeverity',
cvssV4Metrics: 'containers.cna.metrics.cvssV4_0.vectorString',
sourceIdentifier: 'containers.cna.source.discovery',
cpeName: cpeMatchFields.criteria,
isVulnerable: cpeMatchFields.vulnerable,
virtualMatchString: cpeMatchFields.criteria
Expand Down Expand Up @@ -115,7 +112,6 @@ const searchQueryParams = [
'cvssV3_0Metrics',
'cvssV3_1Metrics',
'cvssV4Metrics',
'sourceIdentifier',
'cpeName',
'isVulnerable',
'lastModStartDate',
Expand Down
36 changes: 4 additions & 32 deletions utils/searchQueryBuilder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,34 +306,6 @@ describe('searchQueryBuilder buildQueryStrings() function', () => {
expect(output).toStrictEqual(testQueryString);
});

test('buildQueryStrings() function correctly builds mixed query objects when multiple query string parameters are passed.', () => {
const testParams = {
keywordSearch: 'test* params*',
sourceIdentifier: 'EXTERNAL'
};

const testQueryString = [
{
simple_query_string: {
query: 'test* params*',
default_operator: 'AND',
fields: ['containers.cna.descriptions.value']
}
},
{
query_string: {
query: 'EXTERNAL',
default_operator: 'AND',
fields: ['containers.cna.source.discovery']
}
}
];

const output = queryBuilder.buildQueryStrings(testParams);
expect(output).toHaveLength(2);
expect(output).toStrictEqual(testQueryString);
});

test('buildQueryStrings() ignores virtualMatchString because it is handled as a structured filter.', () => {
const output = queryBuilder.buildQueryStrings({
virtualMatchString: 'cpe:2.3:o:microsoft:windows_10_1809'
Expand All @@ -346,13 +318,13 @@ describe('searchQueryBuilder buildQueryStrings() function', () => {
describe('searchQueryBuilder getFilterFields() function', () => {
test('getFilterFields() function correctly builds array of one matchPhrase object when one parameter is passed.', () => {
const testParams = {
sourceIdentifier: 'EXTERNAL'
cweId: 'CWE-79'
};

const testMatchPhrase = [
{
match_phrase: {
'containers.cna.source.discovery': 'EXTERNAL'
'containers.cna.problemTypes.descriptions.cweId': 'CWE-79'
}
}
];
Expand All @@ -364,14 +336,14 @@ describe('searchQueryBuilder getFilterFields() function', () => {

test('getFilterFields() function correctly builds default and severity filter objects when multiple parameters are passed.', () => {
const testParams = {
sourceIdentifier: 'EXTERNAL',
cweId: 'CWE-79',
cvssV4Severity: 'HIGH'
};

const testMatchPhrase = [
{
match_phrase: {
'containers.cna.source.discovery': 'EXTERNAL'
'containers.cna.problemTypes.descriptions.cweId': 'CWE-79'
}
},
{
Expand Down
Loading