From e8d7c265f2ab5dc5eec327cac8b5bc9bbb4a1ffa 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 1/2] feat(CSAF2.1): #199 test 6.3.15 for CSAF 2.1 --- README.md | 2 +- csaf_2_1/informativeTests.js | 1 + .../informativeTest_6_3_15.js | 131 ++++++++++++++++++ tests/csaf_2_1/informativeTest_6_3_15.js | 53 +++++++ tests/csaf_2_1/oasis.js | 1 - 5 files changed, 186 insertions(+), 2 deletions(-) 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/README.md b/README.md index 6f4f9297..e552357f 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,6 @@ The following tests are not yet implemented and therefore missing: - Informative Test 6.2.13 - Informative Test 6.2.14 -- Informative Test 6.2.15 - Informative Test 6.2.16 - Informative Test 6.2.17 @@ -480,6 +479,7 @@ export const informativeTest_6_3_9: DocumentTest export const informativeTest_6_3_10: DocumentTest export const informativeTest_6_3_11: DocumentTest export const informativeTest_6_3_12: DocumentTest +export const informativeTest_6_3_15: DocumentTest ``` [(back to top)](#bsi-csaf-validator-lib) diff --git a/csaf_2_1/informativeTests.js b/csaf_2_1/informativeTests.js index b3142316..28f11615 100644 --- a/csaf_2_1/informativeTests.js +++ b/csaf_2_1/informativeTests.js @@ -12,3 +12,4 @@ export { informativeTest_6_3_1 } from './informativeTests/informativeTest_6_3_1. export { informativeTest_6_3_2 } from './informativeTests/informativeTest_6_3_2.js' export { informativeTest_6_3_4 } from './informativeTests/informativeTest_6_3_4.js' export { informativeTest_6_3_12 } from './informativeTests/informativeTest_6_3_12.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..de93aa81 --- /dev/null +++ b/csaf_2_1/informativeTests/informativeTest_6_3_15.js @@ -0,0 +1,131 @@ +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, + properties: { + tlp: { + additionalProperties: true, + properties: { + label: { type: 'string' }, + }, + }, + }, + }, + }, + }, + vulnerabilities: { + elements: { + additionalProperties: true, + properties: {}, + optionalProperties: { + metrics: { + elements: { + additionalProperties: true, + optionalProperties: { + content: { + additionalProperties: true, + optionalProperties: { + ssvc_v1: { + additionalProperties: true, + optionalProperties: { + selections: { + elements: { + additionalProperties: true, + 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..c6e8653a --- /dev/null +++ b/tests/csaf_2_1/informativeTest_6_3_15.js @@ -0,0 +1,53 @@ +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) + }) + it('test input schema with not considered json object in vulnerabilities', function () { + assert.equal( + informativeTest_6_3_15({ + document: { + distribution: { + tlp: { + label: 'GREEN', + }, + }, + }, + vulnerabilities: [ + {}, + { + cve: 'CVE-1900-0001', + metrics: [ + { + content: { + ssvc_v1: { + id: 'CVE-1900-0001', + schemaVersion: '1-0-1', + selections: [ + { + name: 'Technical Impact', + namespace: 'ssvc/additional-technical-impacts', + values: ['Total'], + version: '1.0.0', + }, + { + name: 'Technical Impact', + values: ['Total'], + version: '1.0.0', + }, + ], + timestamp: '2024-01-24T10:00:00.000Z', + }, + }, + products: ['CSAFPID-9080700'], + }, + ], + }, + ], + }).infos.length, + 1 + ) + }) +}) diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index 0e9d2e60..5b621df7 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -62,7 +62,6 @@ const excluded = [ '6.3.12', '6.3.13', '6.3.14', - '6.3.15', '6.3.16', '6.3.17', '6.3.18', From 9cd286dc6cc1425df29b7a2d56fe8e0630918ea8 Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:27:43 +0200 Subject: [PATCH 2/2] feat(CSAF2.1): #199 test 6.3.15 for CSAF 2.1 - use ssvc_02 --- .../informativeTest_6_3_15.js | 23 +++++++++++-------- tests/csaf_2_1/informativeTest_6_3_15.js | 4 ++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/csaf_2_1/informativeTests/informativeTest_6_3_15.js b/csaf_2_1/informativeTests/informativeTest_6_3_15.js index de93aa81..dfc306fb 100644 --- a/csaf_2_1/informativeTests/informativeTest_6_3_15.js +++ b/csaf_2_1/informativeTests/informativeTest_6_3_15.js @@ -2,6 +2,8 @@ import Ajv from 'ajv/dist/jtd.js' const ajv = new Ajv() +const PREFIX_EXTENSION_NAMESPACE = 'x_' + /** * @typedef {object} Selection * @property {string} [name] @@ -10,13 +12,13 @@ const ajv = new Ajv() */ /** - * @typedef {object} Ssvc1 + * @typedef {object} Ssvc2 * @property {Array} [selections] */ /** * @typedef {object} MetricContent - * @property {Ssvc1} [ssvc_v1] + * @property {Ssvc2} [ssvc_v2] */ /** @@ -55,7 +57,7 @@ const inputSchema = /** @type {const} */ ({ content: { additionalProperties: true, optionalProperties: { - ssvc_v1: { + ssvc_v2: { additionalProperties: true, optionalProperties: { selections: { @@ -87,13 +89,13 @@ const validateInput = ajv.compile(inputSchema) * @param {string | undefined } namespace */ function namespaceUsesExtension(namespace) { - return namespace ? namespace.includes('/') : false + return namespace ? namespace.startsWith(PREFIX_EXTENSION_NAMESPACE) : 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. + * For each SSVC decision point given under `selections`, it MUST be tested that the `namespace` does not use an extension + * if the document is not labeled `TLP:CLEAR`. + * Namespaces reserved for special purpose MUST be treated as per their definition. * @param {unknown} doc * @returns */ @@ -112,15 +114,16 @@ export function informativeTest_6_3_15(doc) { /** @type {Array | undefined} */ const metrics = vulnerability.metrics metrics?.forEach((metric, metricIndex) => { - const selections = metric?.content?.ssvc_v1?.selections + const selections = metric?.content?.ssvc_v2?.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`, + instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v2/selections/${selectionIndex}/namespace`, + message: + 'namespace uses an extension and document is not labeled TLP:CLEAR', }) } }) diff --git a/tests/csaf_2_1/informativeTest_6_3_15.js b/tests/csaf_2_1/informativeTest_6_3_15.js index c6e8653a..a87fd4d5 100644 --- a/tests/csaf_2_1/informativeTest_6_3_15.js +++ b/tests/csaf_2_1/informativeTest_6_3_15.js @@ -22,13 +22,13 @@ describe('informativeTest_6_3_15', function () { metrics: [ { content: { - ssvc_v1: { + ssvc_v2: { id: 'CVE-1900-0001', schemaVersion: '1-0-1', selections: [ { name: 'Technical Impact', - namespace: 'ssvc/additional-technical-impacts', + namespace: 'x_additional-technical-impacts', values: ['Total'], version: '1.0.0', },