From 62116079deb116e2d39fe13fe7ad94d6318c01aa Mon Sep 17 00:00:00 2001 From: chirschenberger Date: Tue, 17 Jun 2025 12:29:19 +0200 Subject: [PATCH 1/7] feat: add mandatory test 6.1.46 --- csaf_2_1/mandatoryTests.js | 1 + .../mandatoryTests/mandatoryTest_6_1_46.js | 83 +++++++++++++++++++ tests/csaf_2_1/mandatoryTest_6_1_46.js | 8 ++ tests/csaf_2_1/oasis.js | 2 +- 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js create mode 100644 tests/csaf_2_1/mandatoryTest_6_1_46.js diff --git a/csaf_2_1/mandatoryTests.js b/csaf_2_1/mandatoryTests.js index 482f0616..f77efe25 100644 --- a/csaf_2_1/mandatoryTests.js +++ b/csaf_2_1/mandatoryTests.js @@ -60,4 +60,5 @@ export { mandatoryTest_6_1_40 } from './mandatoryTests/mandatoryTest_6_1_40.js' export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js' export { mandatoryTest_6_1_43 } from './mandatoryTests/mandatoryTest_6_1_43.js' export { mandatoryTest_6_1_45 } from './mandatoryTests/mandatoryTest_6_1_45.js' +export { mandatoryTest_6_1_46 } from './mandatoryTests/mandatoryTest_6_1_46.js' export { mandatoryTest_6_1_52 } from './mandatoryTests/mandatoryTest_6_1_52.js' diff --git a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js new file mode 100644 index 00000000..ddea5ca2 --- /dev/null +++ b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js @@ -0,0 +1,83 @@ +import Ajv from 'ajv/dist/jtd.js' +import csafAjv from '../../lib/shared/csafAjv.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: { + vulnerabilities: { + elements: { + additionalProperties: true, + optionalProperties: { + metrics: { + elements: { + additionalProperties: true, + optionalProperties: { + content: { + additionalProperties: true, + properties: { + ssvc_v1: { + additionalProperties: true, + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}) + +const validateInput = ajv.compile(inputSchema) + +const validate_ssvc_v1 = csafAjv.compile({ + $ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point_Value_Selection-1-0-1.schema.json', +}) + +/** + * This implements the mandatory test 6.1.46 of the CSAF 2.1 standard. + * + * @param {unknown} doc + */ +export function mandatoryTest_6_1_46(doc) { + /* + The `ctx` variable holds the state that is accumulated during the test ran and is + finally returned by the function. + */ + const ctx = { + errors: + /** @type {Array<{ instancePath: string; message: string }>} */ ([]), + isValid: true, + } + + if (!validateInput(doc)) { + return ctx + } + + doc.vulnerabilities?.forEach((vulnerability, vulnerabilityIndex) => { + vulnerability.metrics?.forEach((metric, metricIndex) => { + const valid = validate_ssvc_v1(metric.content?.ssvc_v1) + if (!valid) { + ctx.isValid = false + for (const err of validate_ssvc_v1.errors ?? []) { + ctx.errors.push({ + instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v1${err.instancePath}`, + message: err.message ?? '', + }) + } + } + }) + }) + + return ctx +} diff --git a/tests/csaf_2_1/mandatoryTest_6_1_46.js b/tests/csaf_2_1/mandatoryTest_6_1_46.js new file mode 100644 index 00000000..3c2d518d --- /dev/null +++ b/tests/csaf_2_1/mandatoryTest_6_1_46.js @@ -0,0 +1,8 @@ +import assert from 'node:assert/strict' +import { mandatoryTest_6_1_46 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js' + +describe('mandatoryTest_6_1_46', function () { + it('only runs on relevant documents', function () { + assert.equal(mandatoryTest_6_1_46({ document: 'mydoc' }).isValid, true) + }) +}) diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index 42c7d07c..6d00271b 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -21,7 +21,7 @@ const excluded = [ '6.1.37', '6.1.42', '6.1.44', - '6.1.46', + '6.1.45', '6.1.47', '6.1.48', '6.1.49', From 038e78d5a3f75eba0becc7647f416cbc233fe693 Mon Sep 17 00:00:00 2001 From: chirschenberger Date: Tue, 24 Jun 2025 14:22:10 +0200 Subject: [PATCH 2/7] docs: update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb3bd442..5785d2ed 100644 --- a/README.md +++ b/README.md @@ -315,7 +315,7 @@ The following tests are not yet implemented and therefore missing: - Mandatory Test 6.1.27.13 - Mandatory Test 6.1.42 - Mandatory Test 6.1.44 -- Mandatory Test 6.1.46 +- Mandatory Test 6.1.45 - Mandatory Test 6.1.47 - Mandatory Test 6.1.48 - Mandatory Test 6.1.49 @@ -434,6 +434,7 @@ export const mandatoryTest_6_1_40: DocumentTest export const mandatoryTest_6_1_41: DocumentTest export const mandatoryTest_6_1_43: DocumentTest export const mandatoryTest_6_1_45: DocumentTest +export const mandatoryTest_6_1_46: DocumentTest export const mandatoryTest_6_1_52: DocumentTest ``` From aa1340c452b4fda8b6fa4833cdb130a3d5e5803b Mon Sep 17 00:00:00 2001 From: chirschenberger Date: Tue, 24 Jun 2025 17:13:16 +0200 Subject: [PATCH 3/7] feat: change ssvc_v1 property to optionalProperty in inputSchema --- .../mandatoryTests/mandatoryTest_6_1_46.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js index ddea5ca2..ea14e237 100644 --- a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js +++ b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js @@ -22,7 +22,7 @@ const inputSchema = /** @type {const} */ ({ optionalProperties: { content: { additionalProperties: true, - properties: { + optionalProperties: { ssvc_v1: { additionalProperties: true, properties: {}, @@ -66,14 +66,16 @@ export function mandatoryTest_6_1_46(doc) { doc.vulnerabilities?.forEach((vulnerability, vulnerabilityIndex) => { vulnerability.metrics?.forEach((metric, metricIndex) => { - const valid = validate_ssvc_v1(metric.content?.ssvc_v1) - if (!valid) { - ctx.isValid = false - for (const err of validate_ssvc_v1.errors ?? []) { - ctx.errors.push({ - instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v1${err.instancePath}`, - message: err.message ?? '', - }) + if (metric.content?.ssvc_v1) { + const valid = validate_ssvc_v1(metric.content.ssvc_v1) + if (!valid) { + ctx.isValid = false + for (const err of validate_ssvc_v1.errors ?? []) { + ctx.errors.push({ + instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v1${err.instancePath}`, + message: err.message ?? '', + }) + } } } }) From e550ba1b918a9433a707bc6d39c1700fd6926d9c Mon Sep 17 00:00:00 2001 From: chirschenberger Date: Wed, 25 Jun 2025 13:40:34 +0200 Subject: [PATCH 4/7] feat: add test for input schema --- tests/csaf_2_1/mandatoryTest_6_1_46.js | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/csaf_2_1/mandatoryTest_6_1_46.js b/tests/csaf_2_1/mandatoryTest_6_1_46.js index 3c2d518d..622f1c3f 100644 --- a/tests/csaf_2_1/mandatoryTest_6_1_46.js +++ b/tests/csaf_2_1/mandatoryTest_6_1_46.js @@ -1,8 +1,43 @@ import assert from 'node:assert/strict' import { mandatoryTest_6_1_46 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js' +import { expect } from 'chai' + +const validInputSchemaTestWithEmptyVulnerability6_1_46 = { + vulnerabilities: [ + {}, // even this vulnerability is empty, the test should not fail due to the inputSchema + { + metrics: [ + { + content: { + ssvc_v1: { + id: 'CVE-1900-0001', + schemaVersion: '1-0-1', + selections: [ + { + name: 'Mission Impact', + namespace: 'ssvc', + values: ['None'], + version: '1.0.0', + }, + ], + timestamp: '2024-01-24T10:00:00.000Z', + }, + }, + products: ['CSAFPID-9080700'], + }, + ], + }, + ], +} describe('mandatoryTest_6_1_46', function () { it('only runs on relevant documents', function () { assert.equal(mandatoryTest_6_1_46({ document: 'mydoc' }).isValid, true) }) + it('test input schema with empty json object in vulnerabilities', async function () { + const result = mandatoryTest_6_1_46( + validInputSchemaTestWithEmptyVulnerability6_1_46 + ) + expect(result.errors.length).to.eq(0) + }) }) From 5b6d30051f6d2a81c21d1a8a1226502b12a89b98 Mon Sep 17 00:00:00 2001 From: chirschenberger Date: Thu, 26 Jun 2025 12:10:26 +0200 Subject: [PATCH 5/7] feat: change inputSchema test from valid to failing example --- tests/csaf_2_1/mandatoryTest_6_1_46.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tests/csaf_2_1/mandatoryTest_6_1_46.js b/tests/csaf_2_1/mandatoryTest_6_1_46.js index 622f1c3f..496e5381 100644 --- a/tests/csaf_2_1/mandatoryTest_6_1_46.js +++ b/tests/csaf_2_1/mandatoryTest_6_1_46.js @@ -2,24 +2,17 @@ import assert from 'node:assert/strict' import { mandatoryTest_6_1_46 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js' import { expect } from 'chai' -const validInputSchemaTestWithEmptyVulnerability6_1_46 = { +const failingInputSchemaTestWithEmptyVulnerability6_1_46 = { vulnerabilities: [ {}, // even this vulnerability is empty, the test should not fail due to the inputSchema { + cve: 'CVE-1900-0001', metrics: [ { content: { ssvc_v1: { id: 'CVE-1900-0001', schemaVersion: '1-0-1', - selections: [ - { - name: 'Mission Impact', - namespace: 'ssvc', - values: ['None'], - version: '1.0.0', - }, - ], timestamp: '2024-01-24T10:00:00.000Z', }, }, @@ -36,8 +29,8 @@ describe('mandatoryTest_6_1_46', function () { }) it('test input schema with empty json object in vulnerabilities', async function () { const result = mandatoryTest_6_1_46( - validInputSchemaTestWithEmptyVulnerability6_1_46 + failingInputSchemaTestWithEmptyVulnerability6_1_46 ) - expect(result.errors.length).to.eq(0) + expect(result.errors.length).to.eq(1) }) }) From c66091b855df4e84f36abbe89230b51350389d45 Mon Sep 17 00:00:00 2001 From: chirschenberger Date: Thu, 26 Jun 2025 12:19:51 +0200 Subject: [PATCH 6/7] fix: fix doc in inputSchema test --- README.md | 1 - tests/csaf_2_1/mandatoryTest_6_1_46.js | 2 +- tests/csaf_2_1/oasis.js | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 5785d2ed..a100703e 100644 --- a/README.md +++ b/README.md @@ -315,7 +315,6 @@ The following tests are not yet implemented and therefore missing: - Mandatory Test 6.1.27.13 - Mandatory Test 6.1.42 - Mandatory Test 6.1.44 -- Mandatory Test 6.1.45 - Mandatory Test 6.1.47 - Mandatory Test 6.1.48 - Mandatory Test 6.1.49 diff --git a/tests/csaf_2_1/mandatoryTest_6_1_46.js b/tests/csaf_2_1/mandatoryTest_6_1_46.js index 496e5381..5b237ba0 100644 --- a/tests/csaf_2_1/mandatoryTest_6_1_46.js +++ b/tests/csaf_2_1/mandatoryTest_6_1_46.js @@ -4,7 +4,7 @@ import { expect } from 'chai' const failingInputSchemaTestWithEmptyVulnerability6_1_46 = { vulnerabilities: [ - {}, // even this vulnerability is empty, the test should not fail due to the inputSchema + {}, // even this vulnerability is empty, the test should run { cve: 'CVE-1900-0001', metrics: [ diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index 6d00271b..1d98c696 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -21,7 +21,6 @@ const excluded = [ '6.1.37', '6.1.42', '6.1.44', - '6.1.45', '6.1.47', '6.1.48', '6.1.49', From 1306b70819aa39040beea43713b4eb67d3ef96d3 Mon Sep 17 00:00:00 2001 From: chirschenberger Date: Thu, 11 Sep 2025 12:07:09 +0200 Subject: [PATCH 7/7] feat: adapt code to match ssvc v2 --- csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js | 16 ++++++++-------- tests/csaf_2_1/mandatoryTest_6_1_46.js | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js index ea14e237..f94de53b 100644 --- a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js +++ b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js @@ -1,5 +1,5 @@ import Ajv from 'ajv/dist/jtd.js' -import csafAjv from '../../lib/shared/csafAjv.js' +import csafAjv from '../csafAjv.js' const ajv = new Ajv() @@ -23,7 +23,7 @@ const inputSchema = /** @type {const} */ ({ content: { additionalProperties: true, optionalProperties: { - ssvc_v1: { + ssvc_v2: { additionalProperties: true, properties: {}, }, @@ -40,8 +40,8 @@ const inputSchema = /** @type {const} */ ({ const validateInput = ajv.compile(inputSchema) -const validate_ssvc_v1 = csafAjv.compile({ - $ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point_Value_Selection-1-0-1.schema.json', +const validate_ssvc_v2 = csafAjv.compile({ + $ref: 'https://certcc.github.io/SSVC/data/schema/v2/Decision_Point_Value_Selection-2-0-0.schema.json', }) /** @@ -66,13 +66,13 @@ export function mandatoryTest_6_1_46(doc) { doc.vulnerabilities?.forEach((vulnerability, vulnerabilityIndex) => { vulnerability.metrics?.forEach((metric, metricIndex) => { - if (metric.content?.ssvc_v1) { - const valid = validate_ssvc_v1(metric.content.ssvc_v1) + if (metric.content?.ssvc_v2) { + const valid = validate_ssvc_v2(metric.content.ssvc_v2) if (!valid) { ctx.isValid = false - for (const err of validate_ssvc_v1.errors ?? []) { + for (const err of validate_ssvc_v2.errors ?? []) { ctx.errors.push({ - instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v1${err.instancePath}`, + instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v2${err.instancePath}`, message: err.message ?? '', }) } diff --git a/tests/csaf_2_1/mandatoryTest_6_1_46.js b/tests/csaf_2_1/mandatoryTest_6_1_46.js index 5b237ba0..307a48cd 100644 --- a/tests/csaf_2_1/mandatoryTest_6_1_46.js +++ b/tests/csaf_2_1/mandatoryTest_6_1_46.js @@ -10,9 +10,8 @@ const failingInputSchemaTestWithEmptyVulnerability6_1_46 = { metrics: [ { content: { - ssvc_v1: { - id: 'CVE-1900-0001', - schemaVersion: '1-0-1', + ssvc_v2: { + schemaVersion: '2.0.0', timestamp: '2024-01-24T10:00:00.000Z', }, },