Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions csaf_2_1/recommendedTests/recommendedTest_6_2_53.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Ajv } from 'ajv/dist/jtd.js'
import { entries } from '../../rvisc.js'

const ajv = new Ajv()

/** @type {Array<{ system_name: string; text_pattern: RegExp }>} */
const registeredIdSystems = entries.map(
(/** @type {{ system_name: string; text_pattern: string }} */ entry) => ({
system_name: entry.system_name,
text_pattern: new RegExp(entry.text_pattern),
})
)

/*
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: {
ids: {
elements: {
additionalProperties: true,
optionalProperties: {
system_name: { type: 'string' },
text: { type: 'string' },
},
},
},
},
},
},
},
})

const validate = ajv.compile(inputSchema)

/** @typedef {import('ajv/dist/jtd.js').JTDDataType<typeof inputSchema>} InputSchema */
/** @typedef {InputSchema['vulnerabilities'][number]} Vulnerability */

/**
* This implements the recommended test 6.2.53 of the CSAF 2.1 standard.
*
* @param {unknown} doc
*/
export function recommendedTest_6_2_53(doc) {
const ctx = {
warnings:
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
}

if (!validate(doc)) {
return ctx
}

/** @type {Array<Vulnerability>} */
const vulnerabilities = doc.vulnerabilities
vulnerabilities.forEach((vulnerability, vulnIndex) => {
const ids = vulnerability.ids ?? []
ids.forEach((id, idIndex) => {
if (id.system_name === undefined || id.text === undefined) return

const registeredSystem = registeredIdSystems.find(
(entry) => entry.system_name === id.system_name
)
if (!registeredSystem) return

if (!registeredSystem.text_pattern.test(id.text)) {
ctx.warnings.push({
instancePath: `/vulnerabilities/${vulnIndex}/ids/${idIndex}/text`,
message: `the text does not match the text_pattern of the registered ID system "${id.system_name}"`,
})
}
})
})

return ctx
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"mandatoryTests.js",
"optionalTests.js",
"README.md",
"rvisc.js",
"schemaTests.js",
"strip.js",
"validate.js",
Expand Down Expand Up @@ -100,6 +101,10 @@
"types": "./build/lib/shared/cwec.d.ts",
"import": "./lib/shared/cwec.js"
},
"./rvisc.js": {
"types": "./build/rvisc.d.ts",
"import": "./rvisc.js"
},
"./lib/shared/cvss2.js": {
"types": "./build/lib/shared/cvss2.d.ts",
"import": "./lib/shared/cvss2.js"
Expand Down
32 changes: 32 additions & 0 deletions rvisc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const rvisc = {
$schema:
'https://raw.githubusercontent.com/oasis-tcs/csaf/master/registry/id/schema/registry.schema.json',
entries: [
{
common_name: 'OASIS Open CSAF TC GitHub Issues',
example_identifiers: ['#1217'],
published: '2026-01-28T17:45:00Z',
summary:
'Contains identifiers for issues in the official standard repository of the OASIS Open CSAF TC.',
system_name: 'https://github.com/oasis-tcs/csaf',
text_pattern: '^#[1-9]\\d*$',
updated: '2026-01-28T17:45:00Z',
},
{
common_name: 'EUVD IDs',
example_identifiers: ['EUVD-2026-4660'],
published: '2026-01-28T18:00:00Z',
summary:
'Contains identifiers from the European Vulnerability Database (EUVD).',
system_name: 'https://euvd.enisa.europa.eu',
text_pattern: '^EUVD-[1-9][0-9]{3}-[0-9]{4,}$',
updated: '2026-02-06T15:35:00Z',
},
],
last_updated: '2026-02-06T15:35:00Z',
registry_version: 1,
}

export default rvisc

export const entries = rvisc.entries
32 changes: 32 additions & 0 deletions scripts/rvisc-importRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node

import { writeFile, readFile } from 'node:fs/promises'
import prettier from 'prettier'

/**
* Converts the RVISC registry (`csaf/registry/id/registry.json`, part of
* the `csaf/` git subtree and excluded from the npm package) into a plain
* ESM module (`rvisc.js`) that works in Node and in the browser.
*
* Run again after `csaf/registry/id/registry.json` was updated:
*
* node scripts/rvisc-importRegistry.js
*/

const REGISTRY_FILE = 'csaf/registry/id/registry.json'
const OUTPUT_FILE = 'rvisc.js'

const json = JSON.parse(await readFile(REGISTRY_FILE, 'utf-8'))

await writeFile(
OUTPUT_FILE,
prettier.format(
`const rvisc = (${JSON.stringify(
json
)})\n\nexport default rvisc\n\nexport const entries = rvisc.entries`,
{
...(await prettier.resolveConfig(OUTPUT_FILE)),
filepath: OUTPUT_FILE,
}
)
)
46 changes: 46 additions & 0 deletions tests/csaf_2_1/recommendedTest_6_2_53.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import assert from 'node:assert/strict'
import { recommendedTest_6_2_53 } from '../../csaf_2_1/recommendedTests/recommendedTest_6_2_53.js'

describe('recommendedTest_6_2_53', function () {
it('only runs on relevant documents', function () {
assert.equal(recommendedTest_6_2_53({}).warnings.length, 0)
})

it('does not warn when ids are absent', function () {
assert.equal(
recommendedTest_6_2_53({ vulnerabilities: [{}] }).warnings.length,
0
)
})

it('does not warn when text is absent', function () {
assert.equal(
recommendedTest_6_2_53({
vulnerabilities: [
{
ids: [{ system_name: 'https://example.com' }],
},
],
}).warnings.length,
0
)
})

it('does not warn when system_name is not in the registry', function () {
assert.equal(
recommendedTest_6_2_53({
vulnerabilities: [
{
ids: [
{
system_name: 'https://unknown-system.example.com',
text: 'some-text',
},
],
},
],
}).warnings.length,
0
)
})
})