From df901b8ae7e34eed9074e3c7ad46ffe0c8d2a242 Mon Sep 17 00:00:00 2001 From: bendo-eXX Date: Wed, 29 Apr 2026 17:15:11 +0200 Subject: [PATCH 1/3] fix(CSAF2.1): update test 6.2.8 to the new csaf 2.1 schema --- csaf_2_1/informativeTests.js | 2 +- .../informativeTests/informativeTest_6_3_5.js | 44 +++++++++ tests/csaf_2_1/informativeTest_6_3_5.js | 97 +++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 csaf_2_1/informativeTests/informativeTest_6_3_5.js create mode 100644 tests/csaf_2_1/informativeTest_6_3_5.js diff --git a/csaf_2_1/informativeTests.js b/csaf_2_1/informativeTests.js index 0ecb389f..e15078a9 100644 --- a/csaf_2_1/informativeTests.js +++ b/csaf_2_1/informativeTests.js @@ -1,6 +1,5 @@ export { informativeTest_6_3_3, - informativeTest_6_3_5, informativeTest_6_3_6, informativeTest_6_3_7, informativeTest_6_3_8, @@ -11,5 +10,6 @@ export { export { informativeTest_6_3_1 } from './informativeTests/informativeTest_6_3_1.js' 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_5 } from './informativeTests/informativeTest_6_3_5.js' export { informativeTest_6_3_12 } from './informativeTests/informativeTest_6_3_12.js' export { informativeTest_6_3_18 } from './informativeTests/informativeTest_6_3_18.js' diff --git a/csaf_2_1/informativeTests/informativeTest_6_3_5.js b/csaf_2_1/informativeTests/informativeTest_6_3_5.js new file mode 100644 index 00000000..893f324d --- /dev/null +++ b/csaf_2_1/informativeTests/informativeTest_6_3_5.js @@ -0,0 +1,44 @@ +import Ajv from 'ajv/dist/jtd.js' +import { walkHashes } from '../shared/csafHelpers/walkHashes.js' + +const ajv = new Ajv() + +const hashSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + file_hashes: { + elements: { + additionalProperties: true, + properties: {}, + }, + }, + }, +}) + +const validateHash = ajv.compile(hashSchema) + +/** + * @param {unknown} doc + * @returns + */ +export function informativeTest_6_3_5(doc) { + const ctx = { + infos: /** @type {Array<{ message: string; instancePath: string }>} */ ([]), + } + + walkHashes(doc, ({ path, hash }) => { + if (!validateHash(hash)) return + const typedHash = + /** @type {{ file_hashes: Array<{ value?: unknown }> }} */ (hash) + typedHash.file_hashes.forEach((fileHash, fileHashIndex) => { + if (typeof fileHash.value === 'string' && fileHash.value.length < 64) { + ctx.infos.push({ + instancePath: `${path}/${fileHashIndex}/value`, + message: 'use of short hash', + }) + } + }) + }) + + return ctx +} diff --git a/tests/csaf_2_1/informativeTest_6_3_5.js b/tests/csaf_2_1/informativeTest_6_3_5.js new file mode 100644 index 00000000..7c54377f --- /dev/null +++ b/tests/csaf_2_1/informativeTest_6_3_5.js @@ -0,0 +1,97 @@ +import assert from 'node:assert' +import { informativeTest_6_3_5 } from '../../csaf_2_1/informativeTests.js' + +describe('informativeTest_6_3_5', function () { + it('only runs on relevant documents', function () { + assert.equal(informativeTest_6_3_5({ document: 'mydoc' }).infos.length, 0) + }) + + it('skip invalid hash value', function () { + const result = informativeTest_6_3_5({ + product_tree: { + full_product_names: [ + { + name: 'Product A', + product_id: 'CSAFPID-9080700', + product_identification_helper: { + hashes: [ + { + filename: 'product_a.so', + }, + ], + }, + }, + ], + }, + }) + assert.equal(result.infos.length, 0) + }) + + it('detects short hash in product_paths', function () { + const result = informativeTest_6_3_5({ + product_tree: { + product_paths: [ + { + full_product_name: { + name: 'Product A', + product_id: 'CSAFPID-0001', + product_identification_helper: { + hashes: [ + { + file_hashes: [ + { + algorithm: 'md5', + value: 'd41d8cd98f00b204e9800998ecf8427e', + }, + ], + filename: 'product_b.so', + }, + ], + }, + }, + }, + ], + }, + }) + assert.equal(result.infos.length, 1) + assert.equal( + result.infos[0].instancePath, + '/product_tree/product_paths/0/full_product_name/product_identification_helper/hashes/0/file_hashes/0/value' + ) + }) + + it('detects short hash in branches', function () { + const result = informativeTest_6_3_5({ + product_tree: { + branches: [ + { + category: 'vendor', + name: 'Vendor A', + product: { + name: 'Product A', + product_id: 'CSAFPID-0001', + product_identification_helper: { + hashes: [ + { + file_hashes: [ + { + algorithm: 'md5', + value: 'd41d8cd98f00b204e9800998ecf8427e', + }, + ], + filename: 'product_c.so', + }, + ], + }, + }, + }, + ], + }, + }) + assert.equal(result.infos.length, 1) + assert.equal( + result.infos[0].instancePath, + '/product_tree/branches/0/product/product_identification_helper/hashes/0/file_hashes/0/value' + ) + }) +}) From 03ebabfc33dfd2b873c315c02a6cc2c4a3148152 Mon Sep 17 00:00:00 2001 From: bendo-eXX Date: Thu, 30 Apr 2026 09:26:38 +0200 Subject: [PATCH 2/3] fix(CSAF2.1): update walkhashes with type and validation --- .../informativeTests/informativeTest_6_3_5.js | 22 +------------------ csaf_2_1/shared/csafHelpers/walkHashes.js | 20 ++++++++++++++--- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/csaf_2_1/informativeTests/informativeTest_6_3_5.js b/csaf_2_1/informativeTests/informativeTest_6_3_5.js index 893f324d..c9683eb6 100644 --- a/csaf_2_1/informativeTests/informativeTest_6_3_5.js +++ b/csaf_2_1/informativeTests/informativeTest_6_3_5.js @@ -1,22 +1,5 @@ -import Ajv from 'ajv/dist/jtd.js' import { walkHashes } from '../shared/csafHelpers/walkHashes.js' -const ajv = new Ajv() - -const hashSchema = /** @type {const} */ ({ - additionalProperties: true, - properties: { - file_hashes: { - elements: { - additionalProperties: true, - properties: {}, - }, - }, - }, -}) - -const validateHash = ajv.compile(hashSchema) - /** * @param {unknown} doc * @returns @@ -27,10 +10,7 @@ export function informativeTest_6_3_5(doc) { } walkHashes(doc, ({ path, hash }) => { - if (!validateHash(hash)) return - const typedHash = - /** @type {{ file_hashes: Array<{ value?: unknown }> }} */ (hash) - typedHash.file_hashes.forEach((fileHash, fileHashIndex) => { + hash.file_hashes.forEach((fileHash, fileHashIndex) => { if (typeof fileHash.value === 'string' && fileHash.value.length < 64) { ctx.infos.push({ instancePath: `${path}/${fileHashIndex}/value`, diff --git a/csaf_2_1/shared/csafHelpers/walkHashes.js b/csaf_2_1/shared/csafHelpers/walkHashes.js index 314f6a15..b0fc737d 100644 --- a/csaf_2_1/shared/csafHelpers/walkHashes.js +++ b/csaf_2_1/shared/csafHelpers/walkHashes.js @@ -4,9 +4,15 @@ const ajv = new Ajv() const hashSchema = /** @type {const} */ ({ additionalProperties: true, - optionalProperties: { + properties: { file_hashes: { - elements: { additionalProperties: true, properties: {} }, + elements: { + additionalProperties: true, + properties: { + algorithm: { type: 'string' }, + value: { type: 'string' }, + }, + }, }, }, }) @@ -67,10 +73,15 @@ const validateInput = ajv.compile(inputSchema) const validateFullProductName = ajv.compile(fullProductNameSchema) const validateBranch = ajv.compile(branchSchema) const validateProductPath = ajv.compile(productPathSchema) +const validateFileHash = ajv.compile(hashSchema) + +/** + * @typedef {{ file_hashes: Array<{ algorithm: string; value: string }> }} FileHashEntry + */ /** * @param {any} doc - * @param {(params: { path: string; hash: {} }) => void} onHashFound + * @param {(params: { path: string; hash: FileHashEntry }) => void} onHashFound */ export function walkHashes(doc, onHashFound) { if (!validateInput(doc)) { @@ -85,6 +96,7 @@ export function walkHashes(doc, onHashFound) { fullProductName.product_identification_helper?.hashes?.forEach( (hash, hashIndex) => { + if (!validateFileHash(hash)) return onHashFound({ path: `/product_tree/full_product_names/${fullProductNameIndex}/product_identification_helper/hashes/${hashIndex}/file_hashes`, hash, @@ -106,6 +118,7 @@ export function walkHashes(doc, onHashFound) { branch.product?.product_identification_helper?.hashes?.forEach( (hash, hashIndex) => { + if (!validateFileHash(hash)) return onHashFound({ path: `${prefix}${branchIndex}/product/product_identification_helper/hashes/${hashIndex}/file_hashes`, hash, @@ -128,6 +141,7 @@ export function walkHashes(doc, onHashFound) { productPath.full_product_name?.product_identification_helper?.hashes?.forEach( (hash, hashIndex) => { + if (!validateFileHash(hash)) return onHashFound({ path: `/product_tree/product_paths/${productPathIndex}/full_product_name/product_identification_helper/hashes/${hashIndex}/file_hashes`, hash, From 8217654e3c340c14909927845d71f96f743d9265 Mon Sep 17 00:00:00 2001 From: bendo-eXX Date: Thu, 30 Apr 2026 09:44:58 +0200 Subject: [PATCH 3/3] fix(CSAF2.1): update import --- .../recommendedTests/shared/checkForUnsafeHashAlgorithms.js | 2 +- csaf_2_1/shared/csafHelpers/walkHashes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/csaf_2_1/recommendedTests/shared/checkForUnsafeHashAlgorithms.js b/csaf_2_1/recommendedTests/shared/checkForUnsafeHashAlgorithms.js index b8d23516..3b157233 100644 --- a/csaf_2_1/recommendedTests/shared/checkForUnsafeHashAlgorithms.js +++ b/csaf_2_1/recommendedTests/shared/checkForUnsafeHashAlgorithms.js @@ -1,4 +1,4 @@ -import Ajv from 'ajv/dist/jtd.js' +import { Ajv } from 'ajv/dist/jtd.js' import { walkHashes } from '../../shared/csafHelpers/walkHashes.js' const ajv = new Ajv() diff --git a/csaf_2_1/shared/csafHelpers/walkHashes.js b/csaf_2_1/shared/csafHelpers/walkHashes.js index b0fc737d..994a9b44 100644 --- a/csaf_2_1/shared/csafHelpers/walkHashes.js +++ b/csaf_2_1/shared/csafHelpers/walkHashes.js @@ -1,4 +1,4 @@ -import Ajv from 'ajv/dist/jtd.js' +import { Ajv } from 'ajv/dist/jtd.js' const ajv = new Ajv()