From fb4198fa2254b4a4de59db8bbebb8c7ffd309e70 Mon Sep 17 00:00:00 2001 From: bendo-eXX Date: Thu, 31 Jul 2025 11:19:59 +0200 Subject: [PATCH] feat(CSAF2.1): add recommendedTest_6_2_11.js --- README.md | 2 +- .../recommendedTest_6_2_11.js | 102 +++++++++++++++++- tests/csaf_2_1/oasis.js | 1 - tests/csaf_2_1/recommendedTest_6_2_11.js | 80 ++++++++++++++ 4 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 tests/csaf_2_1/recommendedTest_6_2_11.js diff --git a/README.md b/README.md index 6f4f9297..da07589a 100644 --- a/README.md +++ b/README.md @@ -328,7 +328,6 @@ The following tests are not yet implemented and therefore missing: **Recommended Tests** -- Recommended Test 6.2.11 - Recommended Test 6.2.19 - Recommended Test 6.2.20 - Recommended Test 6.2.21 @@ -452,6 +451,7 @@ export const recommendedTest_6_2_7: DocumentTest export const recommendedTest_6_2_8: DocumentTest export const recommendedTest_6_2_9: DocumentTest export const recommendedTest_6_2_10: DocumentTest +export const recommendedTest_6_2_11: DocumentTest export const recommendedTest_6_2_12: DocumentTest export const recommendedTest_6_2_13: DocumentTest export const recommendedTest_6_2_14: DocumentTest diff --git a/csaf_2_1/recommendedTests/recommendedTest_6_2_11.js b/csaf_2_1/recommendedTests/recommendedTest_6_2_11.js index 781d40a9..21b381ba 100644 --- a/csaf_2_1/recommendedTests/recommendedTest_6_2_11.js +++ b/csaf_2_1/recommendedTests/recommendedTest_6_2_11.js @@ -1,8 +1,104 @@ -import { optionalTest_6_2_11 } from '../../optionalTests.js' +import Ajv from 'ajv/dist/jtd.js' + +const ajv = new Ajv() + +const inputSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + document: { + additionalProperties: true, + properties: { + references: { + elements: { + additionalProperties: true, + optionalProperties: { + category: { type: 'string' }, + url: { type: 'string' }, + }, + }, + }, + tracking: { + additionalProperties: true, + optionalProperties: { + id: { type: 'string' }, + }, + }, + }, + }, + }, +}) +const validateInput = ajv.compile(inputSchema) /** - * @param {unknown} doc + * Test for the optional test 6.2.11 + * @param {any} doc */ export function recommendedTest_6_2_11(doc) { - return optionalTest_6_2_11(doc) + const ctx = { + warnings: + /** @type {Array<{ instancePath: string; message: string }>} */ ([]), + } + + if (!validateInput(doc)) { + return ctx + } + + const trackingId = doc.document.tracking.id + if (!trackingId) { + return ctx + } + + const filename = transformTrackingIdToFilename(trackingId) + + const selfReferences = + doc.document.references + ?.map((reference, index) => ({ ...reference, index })) + .filter((ref) => ref.category === 'self') ?? [] + + if (selfReferences.length === 0) { + return ctx + } + + const hasValidSelfReference = selfReferences.some((reference) => { + const url = reference.url || '' + return ( + typeof url === 'string' && + url.startsWith('https://') && + url.endsWith(filename) + ) + }) + + if (hasValidSelfReference) { + return ctx + } + + selfReferences.forEach((reference) => { + const url = reference.url || '' + const referenceIndex = reference.index + + if ( + typeof url !== 'string' || + !url.startsWith('https://') || + !url.endsWith(filename) + ) { + ctx.warnings.push({ + instancePath: `/document/references/${referenceIndex}/url`, + message: + 'The reference category is "self", but the URL does not fulfill the requirements of a valid filename for a CSAF document.', + }) + } + }) + + return ctx +} + +/** * Transforms a tracking ID into a filename. + * + * @param {string} trackingId + * @returns {string} filename + */ +function transformTrackingIdToFilename(trackingId) { + const lowerCase = trackingId.toLowerCase() + const replacedString = lowerCase.replace(/[^+\-a-z0-9]+/g, '_') + return replacedString + '.json' } diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index 0e9d2e60..cda38c8f 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -32,7 +32,6 @@ const excluded = [ '6.1.54', '6.1.55', '6.1.56', - '6.2.11', '6.2.19', '6.2.20', '6.2.21', diff --git a/tests/csaf_2_1/recommendedTest_6_2_11.js b/tests/csaf_2_1/recommendedTest_6_2_11.js new file mode 100644 index 00000000..08b2e5eb --- /dev/null +++ b/tests/csaf_2_1/recommendedTest_6_2_11.js @@ -0,0 +1,80 @@ +import assert from 'node:assert' +import { recommendedTest_6_2_11 } from '../../csaf_2_1/recommendedTests.js' + +describe('recommendedTest_6_2_11', function () { + it('only runs on relevant documents', function () { + assert.equal( + recommendedTest_6_2_11({ vulnerabilities: 'mydoc' }).warnings.length, + 0 + ) + }) + + it('skips documents with empty tracking id', function () { + assert.equal( + recommendedTest_6_2_11({ + document: { + references: [ + { + category: 'self', + url: 'https://example.com/security/data/csaf/2024/oasis_csaf_tc-csaf_2.1-2024-6-2-11-01_1.json', + }, + ], + tracking: { id: '' }, + }, + }).warnings.length, + 0 + ) + }) + + it('skips reference without self references', function () { + assert.equal( + recommendedTest_6_2_11({ + document: { + references: [ + { + category: 'external', + url: 'https://example.com/security/data/csaf/2024/oasis_csaf_tc-csaf_2.1-2024-6-2-11-01_1.json', + }, + ], + tracking: { id: 'OASIS_CSAF_TC-CSAF_2.1-2024-6-2-11-01' }, + }, + }).warnings.length, + 0 + ) + }) + + it('should warn when self reference has empty url', function () { + assert.equal( + recommendedTest_6_2_11({ + document: { + references: [ + { + category: 'self', + url: '', + }, + ], + tracking: { id: 'OASIS_CSAF_TC-CSAF_2.1-2024-6-2-11-01' }, + }, + }).warnings.length, + 1 + ) + }) + + it('skips empty reference', function () { + assert.equal( + recommendedTest_6_2_11({ + document: { + references: [ + {}, + { + category: 'self', + url: 'https://example.com/security/data/csaf/2024/oasis_csaf_tc-csaf_2.1-2024-6-2-11-01_1.json', + }, + ], + tracking: { id: 'OASIS_CSAF_TC-CSAF_2.1-2024-6-2-11-01' }, + }, + }).warnings.length, + 1 + ) + }) +})