From 09962188ffbbbc91299d63a44bb0fa814832876c Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Wed, 14 May 2025 11:29:16 +0200 Subject: [PATCH] feat(CSAF2.1): #199 test 6.3.15 for CSAF 2.1 --- csaf_2_1/informativeTests.js | 1 + .../informativeTest_6_3_15.js | 134 ++++++++++++++++++ tests/csaf_2_1/informativeTest_6_3_15.js | 8 ++ tests/csaf_2_1/oasis.js | 1 - 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 csaf_2_1/informativeTests/informativeTest_6_3_15.js create mode 100644 tests/csaf_2_1/informativeTest_6_3_15.js diff --git a/csaf_2_1/informativeTests.js b/csaf_2_1/informativeTests.js index 12269510..16ac1d34 100644 --- a/csaf_2_1/informativeTests.js +++ b/csaf_2_1/informativeTests.js @@ -11,3 +11,4 @@ export { export { informativeTest_6_3_1 } from './informativeTests/informativeTest_6_3_1.js' export { informativeTest_6_3_4 } from './informativeTests/informativeTest_6_3_4.js' export { informativeTest_6_3_2 } from './informativeTests/informativeTest_6_3_2.js' +export { informativeTest_6_3_15 } from './informativeTests/informativeTest_6_3_15.js' diff --git a/csaf_2_1/informativeTests/informativeTest_6_3_15.js b/csaf_2_1/informativeTests/informativeTest_6_3_15.js new file mode 100644 index 00000000..8085a656 --- /dev/null +++ b/csaf_2_1/informativeTests/informativeTest_6_3_15.js @@ -0,0 +1,134 @@ +import Ajv from 'ajv/dist/jtd.js' + +const ajv = new Ajv() + +/** + * @typedef {object} Selection + * @property {string} [name] + * @property {string} [namespace] + * @property {string} [version] + */ + +/** + * @typedef {object} Ssvc1 + * @property {Array} [selections] + */ + +/** + * @typedef {object} MetricContent + * @property {Ssvc1} [ssvc_v1] + */ + +/** + * @typedef {object} Metric + * @property {MetricContent} [content] + */ + +const inputSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + document: { + additionalProperties: true, + properties: { + distribution: { + additionalProperties: true, + optionalProperties: { + tlp: { + additionalProperties: true, + optionalProperties: { + label: { type: 'string' }, + }, + }, + }, + }, + }, + }, + vulnerabilities: { + elements: { + additionalProperties: true, + properties: {}, + optionalProperties: { + metrics: { + elements: { + additionalProperties: true, + properties: {}, + optionalProperties: { + content: { + additionalProperties: true, + properties: {}, + optionalProperties: { + ssvc_v1: { + additionalProperties: true, + optionalProperties: { + selections: { + elements: { + additionalProperties: true, + properties: {}, + optionalProperties: { + name: { type: 'string' }, + namespace: { type: 'string' }, + version: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}) + +const validateInput = ajv.compile(inputSchema) + +/** + * @param {string | undefined } namespace + */ +function namespaceUsesExtension(namespace) { + return namespace ? namespace.includes('/') : false +} + +/** + * For each SSVC decision point given under selections, + * it MUST be tested the namespace does not use an extension + * if the document is not labeled TLP:CLEAR. + * @param {unknown} doc + * @returns + */ +export function informativeTest_6_3_15(doc) { + const ctx = { + infos: /** @type {Array<{ message: string; instancePath: string }>} */ ([]), + } + + if (!validateInput(doc)) { + return ctx + } + + const vulnerabilities = doc.vulnerabilities + + vulnerabilities.forEach((vulnerability, vulnerabilityIndex) => { + /** @type {Array | undefined} */ + const metrics = vulnerability.metrics + metrics?.forEach((metric, metricIndex) => { + const selections = metric?.content?.ssvc_v1?.selections + selections?.forEach((selection, selectionIndex) => { + if ( + namespaceUsesExtension(selection.namespace) && + doc.document?.distribution?.tlp?.label !== 'TLP:CLEAR' + ) { + ctx.infos.push({ + instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v1/selections/${selectionIndex}/namespace`, + message: `namespace is private and document is not labeled TLP:CLEAR`, + }) + } + }) + }) + }) + + return ctx +} diff --git a/tests/csaf_2_1/informativeTest_6_3_15.js b/tests/csaf_2_1/informativeTest_6_3_15.js new file mode 100644 index 00000000..e19085a0 --- /dev/null +++ b/tests/csaf_2_1/informativeTest_6_3_15.js @@ -0,0 +1,8 @@ +import assert from 'node:assert' +import { informativeTest_6_3_15 } from '../../csaf_2_1/informativeTests.js' + +describe('informativeTest_6_3_15', function () { + it('only runs on relevant documents', function () { + assert.equal(informativeTest_6_3_15({ document: 'mydoc' }).infos.length, 0) + }) +}) diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index ed81ff09..82b6a93b 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -69,7 +69,6 @@ const excluded = [ '6.2.45', '6.2.46', '6.3.14', - '6.3.15', '6.3.12', '6.3.13', '6.3.16',