Skip to content

Commit e960460

Browse files
feat(CSAF2.1): #450 add mandatory test 6.1.56
1 parent 3ba89c6 commit e960460

File tree

5 files changed

+167
-1
lines changed

5 files changed

+167
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ export const mandatoryTest_6_1_41: DocumentTest
433433
export const mandatoryTest_6_1_43: DocumentTest
434434
export const mandatoryTest_6_1_45: DocumentTest
435435
export const mandatoryTest_6_1_52: DocumentTest
436+
export const mandatoryTest_6_1_56: DocumentTest
436437
```
437438
438439
[(back to top)](#bsi-csaf-validator-lib)

csaf_2_1/mandatoryTests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
6161
export { mandatoryTest_6_1_43 } from './mandatoryTests/mandatoryTest_6_1_43.js'
6262
export { mandatoryTest_6_1_45 } from './mandatoryTests/mandatoryTest_6_1_45.js'
6363
export { mandatoryTest_6_1_52 } from './mandatoryTests/mandatoryTest_6_1_52.js'
64+
export { mandatoryTest_6_1_56 } from './mandatoryTests/mandatoryTest_6_1_56.js'
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
3+
/**
4+
* @typedef {string} Product
5+
* /
6+
7+
/** @typedef {import('ajv/dist/jtd.js').JTDDataType<typeof inputSchema>} InputSchema */
8+
9+
/** @typedef {InputSchema['vulnerabilities'][number]} Vulnerability */
10+
11+
/** @typedef {NonNullable<Vulnerability['metrics']>[number]} Metric */
12+
13+
/** @typedef {NonNullable<Metric['content']>} MetricContent */
14+
15+
const jtdAjv = new Ajv()
16+
17+
const inputSchema = /** @type {const} */ ({
18+
additionalProperties: true,
19+
properties: {
20+
vulnerabilities: {
21+
elements: {
22+
additionalProperties: true,
23+
optionalProperties: {
24+
metrics: {
25+
elements: {
26+
additionalProperties: true,
27+
optionalProperties: {
28+
source: {
29+
type: 'string',
30+
},
31+
products: {
32+
elements: { type: 'string' },
33+
},
34+
content: {
35+
additionalProperties: true,
36+
optionalProperties: {
37+
cvss_v2: {
38+
additionalProperties: true,
39+
optionalProperties: {
40+
version: { type: 'string' },
41+
},
42+
},
43+
cvss_v3: {
44+
additionalProperties: true,
45+
optionalProperties: {
46+
version: { type: 'string' },
47+
},
48+
},
49+
cvss_v4: {
50+
additionalProperties: true,
51+
optionalProperties: {
52+
version: { type: 'string' },
53+
},
54+
},
55+
qualitative_severity_rating: {
56+
type: 'string',
57+
},
58+
},
59+
},
60+
},
61+
},
62+
},
63+
},
64+
},
65+
},
66+
},
67+
})
68+
69+
const validate = jtdAjv.compile(inputSchema)
70+
71+
/**
72+
* For each item in `/vulnerabilities` it MUST be tested that no Qualitative Severity Rating and CVSS values are
73+
* listed for the tuple of Product ID and source.
74+
* @param {unknown} doc
75+
*/
76+
export function mandatoryTest_6_1_56(doc) {
77+
const ctx = {
78+
errors:
79+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
80+
isValid: true,
81+
}
82+
83+
/** @type {Array<{ message: string; instancePath: string }>} */
84+
const errors = []
85+
86+
if (!validate(doc)) {
87+
return ctx
88+
}
89+
90+
/** @type {Array<Vulnerability>} */
91+
const vulnerabilities = doc.vulnerabilities
92+
93+
/**
94+
* Create a unique string for the tuple of productId and source
95+
* to compare them easily
96+
* @param {string} productId
97+
* @param {string | undefined} source
98+
*
99+
* @return string
100+
*/
101+
function createTupleStringForProductAndSource(productId, source) {
102+
return JSON.stringify({ productId: productId, source: source ?? '' })
103+
}
104+
105+
/**
106+
*
107+
* @param {Metric} metric
108+
* @returns {boolean}
109+
*/
110+
function hasCvssContent(metric) {
111+
return (
112+
metric.content?.cvss_v2?.version !== undefined ||
113+
metric.content?.cvss_v3?.version !== undefined ||
114+
metric.content?.cvss_v4?.version !== undefined
115+
)
116+
}
117+
118+
vulnerabilities.forEach((vulnerabilityItem, vulnerabilityIndex) => {
119+
/** @type {Map<string,string>} */
120+
const productIdServiceTuplesCvss = new Map()
121+
/** @type {Map<string,string>} */
122+
const productIdServiceTuplesRating = new Map()
123+
124+
/** @type {Array<Metric> | undefined} */
125+
const metrics = vulnerabilityItem.metrics
126+
metrics?.forEach((metric, metricIndex) => {
127+
/** @type {Array<Product> | undefined} */
128+
const productsOfMetric = metric.products
129+
productsOfMetric?.forEach((product, productIndex) => {
130+
if (hasCvssContent(metric)) {
131+
productIdServiceTuplesCvss.set(
132+
createTupleStringForProductAndSource(product, metric.source),
133+
`/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/products/${productIndex}`
134+
)
135+
}
136+
if (metric.content?.qualitative_severity_rating) {
137+
productIdServiceTuplesRating.set(
138+
createTupleStringForProductAndSource(product, metric.source),
139+
`/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/products/${productIndex}`
140+
)
141+
}
142+
})
143+
})
144+
145+
productIdServiceTuplesCvss.forEach((value, key) => {
146+
if (productIdServiceTuplesRating.has(key))
147+
errors.push({
148+
message:
149+
'in the metrics of the vulnerability a Qualitative Severity Rating and CVSS value' +
150+
'with the same product id and source is used.',
151+
instancePath: value,
152+
})
153+
})
154+
})
155+
156+
return { errors: errors, isValid: errors.length === 0 }
157+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import assert from 'node:assert/strict'
2+
import { mandatoryTest_6_1_56 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js'
3+
4+
describe('mandatoryTest_6_1_56', function () {
5+
it('only runs on relevant documents', function () {
6+
assert.equal(mandatoryTest_6_1_56({ document: 'mydoc' }).isValid, true)
7+
})
8+
})

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ const excluded = [
3030
'6.1.53',
3131
'6.1.54',
3232
'6.1.55',
33-
'6.1.56',
3433
'6.2.11',
3534
'6.2.19',
3635
'6.2.20',

0 commit comments

Comments
 (0)