From bccb2c63856e67cf36d698d850571f534608907c Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Fri, 22 May 2026 14:09:14 +0200 Subject: [PATCH] feat(CSAF2.1): #622 CSAF 2.1 add mandatory test 6.1.27.6 --- csaf_2_1/mandatoryTests.js | 2 +- .../mandatoryTests/mandatoryTest_6_1_27_6.js | 76 +++++++++++++++++++ tests/csaf_2_1/mandatoryTest_6_1_27_6.js | 36 +++++++++ tests/csaf_2_1/oasis.js | 1 - 4 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 csaf_2_1/mandatoryTests/mandatoryTest_6_1_27_6.js create mode 100644 tests/csaf_2_1/mandatoryTest_6_1_27_6.js diff --git a/csaf_2_1/mandatoryTests.js b/csaf_2_1/mandatoryTests.js index 5a5a8a7b..41ce1182 100644 --- a/csaf_2_1/mandatoryTests.js +++ b/csaf_2_1/mandatoryTests.js @@ -20,7 +20,6 @@ export { mandatoryTest_6_1_27_2, mandatoryTest_6_1_27_3, mandatoryTest_6_1_27_4, - mandatoryTest_6_1_27_6, mandatoryTest_6_1_27_7, mandatoryTest_6_1_27_8, mandatoryTest_6_1_27_9, @@ -43,6 +42,7 @@ export { mandatoryTest_6_1_10 } from './mandatoryTests/mandatoryTest_6_1_10.js' export { mandatoryTest_6_1_11 } from './mandatoryTests/mandatoryTest_6_1_11.js' export { mandatoryTest_6_1_13 } from './mandatoryTests/mandatoryTest_6_1_13.js' export { mandatoryTest_6_1_27_5 } from './mandatoryTests/mandatoryTest_6_1_27_5.js' +export { mandatoryTest_6_1_27_6 } from './mandatoryTests/mandatoryTest_6_1_27_6.js' export { mandatoryTest_6_1_27_12 } from './mandatoryTests/mandatoryTest_6_1_27_12.js' export { mandatoryTest_6_1_27_14 } from './mandatoryTests/mandatoryTest_6_1_27_14.js' export { mandatoryTest_6_1_27_15 } from './mandatoryTests/mandatoryTest_6_1_27_15.js' diff --git a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_27_6.js b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_27_6.js new file mode 100644 index 00000000..66b61141 --- /dev/null +++ b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_27_6.js @@ -0,0 +1,76 @@ +import { Ajv } from 'ajv/dist/jtd.js' + +const ajv = new Ajv() + +/* + This is the jtd schema that needs to match the input document so that the + test is activated. If this schema doesn't match it normally means that the input + document does not validate against the csaf json schema or optional fields that + the test checks are not present. + */ +const inputSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + document: { + additionalProperties: true, + properties: { + category: { + type: 'string', + }, + }, + }, + vulnerabilities: { + elements: { + additionalProperties: true, + optionalProperties: { + product_status: { + elements: { + additionalProperties: true, + properties: {}, + }, + }, + }, + }, + }, + }, +}) + +const validate = ajv.compile(inputSchema) +/** + * @param {any} doc + */ +export function mandatoryTest_6_1_27_6(doc) { + /** @type {Array<{ message: string; instancePath: string }>} */ + const errors = [] + let isValid = true + + if (!validate(doc)) return { errors, isValid } + + const checkedDocumentCategories = new Set([ + 'csaf_security_advisory', + 'csaf_vex', + 'csaf_deprecated_security_advisory', + ]) + + if (!checkedDocumentCategories.has(doc.document?.category)) { + return { errors, isValid } + } + + const vulnerabilities = doc.vulnerabilities + if (Array.isArray(vulnerabilities)) { + vulnerabilities.forEach((vulnerability, vulnerabilityIndex) => { + if ( + !vulnerability.product_status || + vulnerability.product_status.length === 0 + ) { + isValid = false + errors.push({ + instancePath: `/vulnerabilities/${vulnerabilityIndex}`, + message: 'needs a `notes` attribute', + }) + } + }) + } + + return { errors, isValid } +} diff --git a/tests/csaf_2_1/mandatoryTest_6_1_27_6.js b/tests/csaf_2_1/mandatoryTest_6_1_27_6.js new file mode 100644 index 00000000..e4ef06b3 --- /dev/null +++ b/tests/csaf_2_1/mandatoryTest_6_1_27_6.js @@ -0,0 +1,36 @@ +import assert from 'node:assert/strict' +import { mandatoryTest_6_1_27_6 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_27_6.js' + +describe('mandatoryTest_6_1_27_6', function () { + it('only runs on relevant documents', function () { + assert.equal(mandatoryTest_6_1_27_6({ document: 'mydoc' }).isValid, true) + }) + + it('returns valid for documents with irrelevant category', function () { + assert.equal( + mandatoryTest_6_1_27_6({ + document: { category: 'csaf_base' }, + vulnerabilities: [{}], + }).isValid, + true + ) + }) + + it('returns invalid when vulnerability has no product_status', function () { + const result = mandatoryTest_6_1_27_6({ + document: { category: 'csaf_security_advisory' }, + vulnerabilities: [{}], + }) + assert.equal(result.isValid, false) + assert.equal(result.errors.length, 1) + }) + + it('returns invalid when vulnerability has empty product_status array', function () { + const result = mandatoryTest_6_1_27_6({ + document: { category: 'csaf_security_advisory' }, + vulnerabilities: [{ product_status: [] }], + }) + assert.equal(result.isValid, false) + assert.equal(result.errors.length, 1) + }) +}) diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index d13519a2..fba3432f 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -13,7 +13,6 @@ const excluded = [ '6.1.26', '6.1.27.3', '6.1.27.4', - '6.1.27.6', '6.1.27.11', '6.1.27.13', '6.1.37',