Skip to content

Commit 655f918

Browse files
committed
feat: add mandatory test 6.1.51
1 parent 573df9c commit 655f918

File tree

5 files changed

+264
-3
lines changed

5 files changed

+264
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,6 @@ The following tests are not yet implemented and therefore missing:
331331
- Mandatory Test 6.1.48
332332
- Mandatory Test 6.1.49
333333
- Mandatory Test 6.1.50
334-
- Mandatory Test 6.1.51
335334
- Mandatory Test 6.1.52
336335
- Mandatory Test 6.1.53
337336
- Mandatory Test 6.1.54
@@ -435,6 +434,7 @@ export const mandatoryTest_6_1_38: DocumentTest
435434
export const mandatoryTest_6_1_39: DocumentTest
436435
export const mandatoryTest_6_1_40: DocumentTest
437436
export const mandatoryTest_6_1_41: DocumentTest
437+
export const mandatoryTest_6_1_51: DocumentTest
438438
```
439439
440440
[(back to top)](#bsi-csaf-validator-lib)

csaf_2_1/mandatoryTests.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,16 @@ export {
3737
export { mandatoryTest_6_1_1 } from './mandatoryTests/mandatoryTest_6_1_1.js'
3838
export { mandatoryTest_6_1_7 } from './mandatoryTests/mandatoryTest_6_1_7.js'
3939
export { mandatoryTest_6_1_8 } from './mandatoryTests/mandatoryTest_6_1_8.js'
40+
export { mandatoryTest_6_1_9 } from './mandatoryTests/mandatoryTest_6_1_9.js'
4041
export { mandatoryTest_6_1_11 } from './mandatoryTests/mandatoryTest_6_1_11.js'
4142
export { mandatoryTest_6_1_13 } from './mandatoryTests/mandatoryTest_6_1_13.js'
4243
export { mandatoryTest_6_1_10 } from './mandatoryTests/mandatoryTest_6_1_10.js'
4344
export { mandatoryTest_6_1_34 } from './mandatoryTests/mandatoryTest_6_1_34.js'
4445
export { mandatoryTest_6_1_35 } from './mandatoryTests/mandatoryTest_6_1_35.js'
45-
export { mandatoryTest_6_1_9 } from './mandatoryTests/mandatoryTest_6_1_9.js'
4646
export { mandatoryTest_6_1_36 } from './mandatoryTests/mandatoryTest_6_1_36.js'
4747
export { mandatoryTest_6_1_37 } from './mandatoryTests/mandatoryTest_6_1_37.js'
4848
export { mandatoryTest_6_1_38 } from './mandatoryTests/mandatoryTests_6_1_38.js'
4949
export { mandatoryTest_6_1_39 } from './mandatoryTests/mandatoryTest_6_1_39.js'
5050
export { mandatoryTest_6_1_40 } from './mandatoryTests/mandatoryTest_6_1_40.js'
5151
export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
52+
export { mandatoryTest_6_1_51 } from './mandatoryTests/mandatoryTest_6_1_51.js'
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
import { compareZonedDateTimes } from '../../lib/shared/dateHelper.js'
3+
4+
const ajv = new Ajv()
5+
6+
/*
7+
This is the jtd schema that needs to match the input document so that the
8+
test is activated. If this schema doesn't match it normally means that the input
9+
document does not validate against the csaf json schema or optional fields that
10+
the test checks are not present.
11+
*/
12+
const inputSchema = /** @type {const} */ ({
13+
additionalProperties: true,
14+
properties: {
15+
document: {
16+
additionalProperties: true,
17+
properties: {
18+
tracking: {
19+
additionalProperties: true,
20+
properties: {
21+
revision_history: {
22+
elements: {
23+
additionalProperties: true,
24+
optionalProperties: {
25+
date: { type: 'string' },
26+
},
27+
},
28+
},
29+
status: { type: 'string' },
30+
},
31+
},
32+
},
33+
},
34+
vulnerabilities: {
35+
elements: {
36+
additionalProperties: true,
37+
optionalProperties: {
38+
metrics: {
39+
elements: {
40+
additionalProperties: true,
41+
optionalProperties: {
42+
content: {
43+
additionalProperties: true,
44+
optionalProperties: {
45+
epss: {
46+
additionalProperties: true,
47+
optionalProperties: {
48+
timestamp: { type: 'string' },
49+
},
50+
},
51+
},
52+
},
53+
},
54+
},
55+
},
56+
},
57+
},
58+
},
59+
},
60+
})
61+
62+
const validate = ajv.compile(inputSchema)
63+
64+
/**
65+
* This implements the mandatory test 6.1.51 of the CSAF 2.1 standard.
66+
*
67+
* @param {any} doc
68+
*/
69+
export function mandatoryTest_6_1_51(doc) {
70+
/*
71+
The `ctx` variable holds the state that is accumulated during the test ran and is
72+
finally returned by the function.
73+
*/
74+
const ctx = {
75+
errors:
76+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
77+
isValid: true,
78+
}
79+
80+
if (!validate(doc)) {
81+
return ctx
82+
}
83+
const status = doc.document.tracking.status
84+
if (status !== 'final' && status !== 'interim') {
85+
return ctx
86+
}
87+
88+
const newestRevisionHistoryItem = doc.document.tracking.revision_history
89+
.filter((item) => item.date != null)
90+
.slice()
91+
.sort((a, z) =>
92+
compareZonedDateTimes(
93+
/** @type {string} */ (z.date),
94+
/** @type {string} */ (a.date)
95+
)
96+
)[0]
97+
98+
doc.vulnerabilities?.forEach((vulnerability, vulnerabilityIndex) => {
99+
const metrics = vulnerability.metrics || []
100+
metrics.forEach((metric, metricIdx) => {
101+
const epss = metric.content?.epss || {}
102+
if (
103+
epss.timestamp &&
104+
newestRevisionHistoryItem &&
105+
compareZonedDateTimes(
106+
/** @type {string} */ (newestRevisionHistoryItem.date),
107+
/** @type {string} */ epss.timestamp
108+
) < 0
109+
) {
110+
ctx.isValid = false
111+
ctx.errors.push({
112+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIdx}/content/epss/timestamp`,
113+
message: `the status is ${status}, but the EPSS 'timestamp' is newer than the newest revision history date`,
114+
})
115+
}
116+
})
117+
})
118+
119+
return ctx
120+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import assert from 'node:assert/strict'
2+
import { mandatoryTest_6_1_51 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_51.js'
3+
4+
describe('mandatoryTest_6_1_51', function () {
5+
it('only runs on relevant documents', function () {
6+
assert.equal(mandatoryTest_6_1_51({ document: 'mydoc' }).isValid, true)
7+
})
8+
9+
it('skips status draft', function () {
10+
assert.equal(
11+
mandatoryTest_6_1_51({
12+
document: {
13+
tracking: {
14+
revision_history: [],
15+
status: 'draft',
16+
},
17+
},
18+
vulnerabilities: [],
19+
}).isValid,
20+
true
21+
)
22+
})
23+
24+
it('skips empty revision_history object', function () {
25+
assert.equal(
26+
mandatoryTest_6_1_51({
27+
document: {
28+
tracking: {
29+
revision_history: [
30+
{}, // should be ignored
31+
{ date: '2024-01-24T10:00:00.000Z' },
32+
],
33+
status: 'final',
34+
},
35+
},
36+
vulnerabilities: [
37+
{
38+
metrics: [
39+
{
40+
content: {
41+
epss: {
42+
timestamp: '2024-01-24T12:34:56.789Z',
43+
},
44+
},
45+
},
46+
],
47+
},
48+
],
49+
}).isValid,
50+
false
51+
)
52+
})
53+
54+
it('skips empty vulnerability object', function () {
55+
assert.equal(
56+
mandatoryTest_6_1_51({
57+
document: {
58+
tracking: {
59+
revision_history: [{ date: '2024-01-24T10:00:00.000Z' }],
60+
status: 'final',
61+
},
62+
},
63+
vulnerabilities: [
64+
{}, // should be ignored
65+
{
66+
metrics: [
67+
{
68+
content: {
69+
epss: {
70+
timestamp: '2024-01-24T12:34:56.789Z',
71+
},
72+
},
73+
},
74+
],
75+
},
76+
],
77+
}).isValid,
78+
false
79+
)
80+
})
81+
82+
it('skips empty metrics object', function () {
83+
assert.equal(
84+
mandatoryTest_6_1_51({
85+
document: {
86+
tracking: {
87+
revision_history: [{ date: '2024-01-24T10:00:00.000Z' }],
88+
status: 'final',
89+
},
90+
},
91+
vulnerabilities: [
92+
{
93+
metrics: [
94+
{}, // should be ignored
95+
{
96+
content: {
97+
epss: {
98+
timestamp: '2024-01-24T12:34:56.789Z',
99+
},
100+
},
101+
},
102+
],
103+
},
104+
],
105+
}).isValid,
106+
false
107+
)
108+
})
109+
110+
it('skips empty epss object', function () {
111+
assert.equal(
112+
mandatoryTest_6_1_51({
113+
document: {
114+
tracking: {
115+
revision_history: [{ date: '2024-01-24T10:00:00.000Z' }],
116+
status: 'final',
117+
},
118+
},
119+
vulnerabilities: [
120+
{
121+
metrics: [
122+
{
123+
content: {
124+
epss: {}, // should be ignored
125+
},
126+
},
127+
{
128+
content: {
129+
epss: {
130+
timestamp: '2024-01-24T12:34:56.789Z',
131+
},
132+
},
133+
},
134+
],
135+
},
136+
],
137+
}).isValid,
138+
false
139+
)
140+
})
141+
})

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ const excluded = [
3636
'6.1.48',
3737
'6.1.49',
3838
'6.1.50',
39-
'6.1.51',
4039
'6.1.52',
4140
'6.1.53',
4241
'6.1.54',

0 commit comments

Comments
 (0)