From 87bf6e278451d0c429078edd9521cc8c9a3aa5f0 Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Wed, 17 Sep 2025 06:30:00 +0200 Subject: [PATCH 1/2] feat(CSAF2.1): #450 add mandatory test 6.1.56 --- README.md | 1 + csaf_2_1/mandatoryTests.js | 1 + .../mandatoryTests/mandatoryTest_6_1_56.js | 157 ++++++++++++++++++ tests/csaf_2_1/mandatoryTest_6_1_56.js | 8 + tests/csaf_2_1/oasis.js | 1 - 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js create mode 100644 tests/csaf_2_1/mandatoryTest_6_1_56.js diff --git a/README.md b/README.md index afdaa0c6..1db3f66c 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,7 @@ export const mandatoryTest_6_1_46: DocumentTest export const mandatoryTest_6_1_51: DocumentTest export const mandatoryTest_6_1_52: DocumentTest export const mandatoryTest_6_1_53: DocumentTest +export const mandatoryTest_6_1_56: DocumentTest export const mandatoryTest_6_1_57: DocumentTest export const mandatoryTest_6_1_58: DocumentTest export const mandatoryTest_6_1_61: DocumentTest diff --git a/csaf_2_1/mandatoryTests.js b/csaf_2_1/mandatoryTests.js index 60b53fd3..3f301ee1 100644 --- a/csaf_2_1/mandatoryTests.js +++ b/csaf_2_1/mandatoryTests.js @@ -66,6 +66,7 @@ export { mandatoryTest_6_1_46 } from './mandatoryTests/mandatoryTest_6_1_46.js' export { mandatoryTest_6_1_51 } from './mandatoryTests/mandatoryTest_6_1_51.js' export { mandatoryTest_6_1_52 } from './mandatoryTests/mandatoryTest_6_1_52.js' export { mandatoryTest_6_1_53 } from './mandatoryTests/mandatoryTest_6_1_53.js' +export { mandatoryTest_6_1_56 } from './mandatoryTests/mandatoryTest_6_1_56.js' export { mandatoryTest_6_1_57 } from './mandatoryTests/mandatoryTest_6_1_57.js' export { mandatoryTest_6_1_58 } from './mandatoryTests/mandatoryTest_6_1_58.js' export { mandatoryTest_6_1_61 } from './mandatoryTests/mandatoryTest_6_1_61.js' diff --git a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js new file mode 100644 index 00000000..51a72925 --- /dev/null +++ b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js @@ -0,0 +1,157 @@ +import Ajv from 'ajv/dist/jtd.js' + +/** + * @typedef {string} Product + * / + + /** @typedef {import('ajv/dist/jtd.js').JTDDataType} InputSchema */ + +/** @typedef {InputSchema['vulnerabilities'][number]} Vulnerability */ + +/** @typedef {NonNullable[number]} Metric */ + +/** @typedef {NonNullable} MetricContent */ + +const jtdAjv = new Ajv() + +const inputSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + vulnerabilities: { + elements: { + additionalProperties: true, + optionalProperties: { + metrics: { + elements: { + additionalProperties: true, + optionalProperties: { + source: { + type: 'string', + }, + products: { + elements: { type: 'string' }, + }, + content: { + additionalProperties: true, + optionalProperties: { + cvss_v2: { + additionalProperties: true, + optionalProperties: { + version: { type: 'string' }, + }, + }, + cvss_v3: { + additionalProperties: true, + optionalProperties: { + version: { type: 'string' }, + }, + }, + cvss_v4: { + additionalProperties: true, + optionalProperties: { + version: { type: 'string' }, + }, + }, + qualitative_severity_rating: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}) + +const validate = jtdAjv.compile(inputSchema) + +/** + * For each item in `/vulnerabilities` it MUST be tested that no Qualitative Severity Rating and CVSS values are + * listed for the tuple of Product ID and source. + * @param {unknown} doc + */ +export function mandatoryTest_6_1_56(doc) { + const ctx = { + errors: + /** @type {Array<{ instancePath: string; message: string }>} */ ([]), + isValid: true, + } + + /** @type {Array<{ message: string; instancePath: string }>} */ + const errors = [] + + if (!validate(doc)) { + return ctx + } + + /** @type {Array} */ + const vulnerabilities = doc.vulnerabilities + + /** + * Create a unique string for the tuple of productId and source + * to compare them easily + * @param {string} productId + * @param {string | undefined} source + * + * @return string + */ + function createTupleStringForProductAndSource(productId, source) { + return JSON.stringify({ productId: productId, source: source ?? '' }) + } + + /** + * + * @param {Metric} metric + * @returns {boolean} + */ + function hasCvssContent(metric) { + return ( + metric.content?.cvss_v2?.version !== undefined || + metric.content?.cvss_v3?.version !== undefined || + metric.content?.cvss_v4?.version !== undefined + ) + } + + vulnerabilities.forEach((vulnerabilityItem, vulnerabilityIndex) => { + /** @type {Map} */ + const productIdServiceTuplesCvss = new Map() + /** @type {Map} */ + const productIdServiceTuplesRating = new Map() + + /** @type {Array | undefined} */ + const metrics = vulnerabilityItem.metrics + metrics?.forEach((metric, metricIndex) => { + /** @type {Array | undefined} */ + const productsOfMetric = metric.products + productsOfMetric?.forEach((product, productIndex) => { + if (hasCvssContent(metric)) { + productIdServiceTuplesCvss.set( + createTupleStringForProductAndSource(product, metric.source), + `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/products/${productIndex}` + ) + } + if (metric.content?.qualitative_severity_rating) { + productIdServiceTuplesRating.set( + createTupleStringForProductAndSource(product, metric.source), + `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/products/${productIndex}` + ) + } + }) + }) + + productIdServiceTuplesCvss.forEach((value, key) => { + if (productIdServiceTuplesRating.has(key)) + errors.push({ + message: + 'in the metrics of the vulnerability a Qualitative Severity Rating and CVSS value' + + 'with the same product id and source is used.', + instancePath: value, + }) + }) + }) + + return { errors: errors, isValid: errors.length === 0 } +} diff --git a/tests/csaf_2_1/mandatoryTest_6_1_56.js b/tests/csaf_2_1/mandatoryTest_6_1_56.js new file mode 100644 index 00000000..4072cb02 --- /dev/null +++ b/tests/csaf_2_1/mandatoryTest_6_1_56.js @@ -0,0 +1,8 @@ +import assert from 'node:assert/strict' +import { mandatoryTest_6_1_56 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js' + +describe('mandatoryTest_6_1_56', function () { + it('only runs on relevant documents', function () { + assert.equal(mandatoryTest_6_1_56({ document: 'mydoc' }).isValid, true) + }) +}) diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index 51e4d5bc..76bb457c 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -20,7 +20,6 @@ const excluded = [ '6.1.53', '6.1.54', '6.1.55', - '6.1.56', '6.1.59', '6.1.60.1', '6.1.60.2', From a17c5c3c85879feb36a16043e25dc176893f63e9 Mon Sep 17 00:00:00 2001 From: rschneider <97682836+rainer-exxcellent@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:25:38 +0100 Subject: [PATCH 2/2] feat(CSAF2.1): #450 add mandatory test 6.1.56 - removed unused type, add method comment --- csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js index 51a72925..23753b61 100644 --- a/csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js +++ b/csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js @@ -1,17 +1,13 @@ import Ajv from 'ajv/dist/jtd.js' -/** - * @typedef {string} Product - * / +/** @typedef {string} Product - /** @typedef {import('ajv/dist/jtd.js').JTDDataType} InputSchema */ +/** @typedef {import('ajv/dist/jtd.js').JTDDataType} InputSchema */ /** @typedef {InputSchema['vulnerabilities'][number]} Vulnerability */ /** @typedef {NonNullable[number]} Metric */ -/** @typedef {NonNullable} MetricContent */ - const jtdAjv = new Ajv() const inputSchema = /** @type {const} */ ({ @@ -103,7 +99,7 @@ export function mandatoryTest_6_1_56(doc) { } /** - * + * check whether the given metric contains a cvss (v2, v3 or v4) content * @param {Metric} metric * @returns {boolean} */ @@ -146,7 +142,7 @@ export function mandatoryTest_6_1_56(doc) { if (productIdServiceTuplesRating.has(key)) errors.push({ message: - 'in the metrics of the vulnerability a Qualitative Severity Rating and CVSS value' + + 'in the metrics of the vulnerability a Qualitative Severity Rating and CVSS value ' + 'with the same product id and source is used.', instancePath: value, })