diff --git a/README.md b/README.md index 9dfcb80..6201913 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 1c41dd0..61279e4 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -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": { @@ -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", @@ -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, @@ -503,4 +496,4 @@ } } } -} +} \ No newline at end of file diff --git a/api-docs/openapi.test.js b/api-docs/openapi.test.js index 060097e..0b49ee4 100644 --- a/api-docs/openapi.test.js +++ b/api-docs/openapi.test.js @@ -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'); @@ -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) => { @@ -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'); diff --git a/controllers/middleware.test.js b/controllers/middleware.test.js index 60ddf3c..5079be4 100644 --- a/controllers/middleware.test.js +++ b/controllers/middleware.test.js @@ -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'], @@ -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([ @@ -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 diff --git a/package-lock.json b/package-lock.json index bae0a62..61f936e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cve-search-api", - "version": "0.3.0-beta", + "version": "0.4.0-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cve-search-api", - "version": "0.3.0-beta", + "version": "0.4.0-beta", "dependencies": { "cors": "^2.8.5", "cve-core": "github:CVEProject/cve-core.git#dev-package-test", diff --git a/package.json b/package.json index de45e6f..2a59a9f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/routes/index.js b/routes/index.js index 7cc0568..adca945 100644 --- a/routes/index.js +++ b/routes/index.js @@ -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", diff --git a/routes/swagger.js b/routes/swagger.js index ecfe77e..1cf65db 100644 --- a/routes/swagger.js +++ b/routes/swagger.js @@ -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, '..'); @@ -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.', { @@ -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: { diff --git a/utils/constants.js b/utils/constants.js index e3f824e..85ae9f3 100644 --- a/utils/constants.js +++ b/utils/constants.js @@ -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, @@ -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 @@ -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 @@ -115,7 +112,6 @@ const searchQueryParams = [ 'cvssV3_0Metrics', 'cvssV3_1Metrics', 'cvssV4Metrics', - 'sourceIdentifier', 'cpeName', 'isVulnerable', 'lastModStartDate', diff --git a/utils/searchQueryBuilder.test.js b/utils/searchQueryBuilder.test.js index 9430664..b26f879 100644 --- a/utils/searchQueryBuilder.test.js +++ b/utils/searchQueryBuilder.test.js @@ -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' @@ -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' } } ]; @@ -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' } }, {