Skip to content

Commit c75bb20

Browse files
committed
feat: add mandatory test 6.1.53
1 parent c8064b6 commit c75bb20

File tree

5 files changed

+176
-2
lines changed

5 files changed

+176
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,6 @@ The following tests are not yet implemented and therefore missing:
333333
- Mandatory Test 6.1.50
334334
- Mandatory Test 6.1.51
335335
- Mandatory Test 6.1.52
336-
- Mandatory Test 6.1.53
337336
- Mandatory Test 6.1.54
338337
- Mandatory Test 6.1.55
339338
@@ -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_53: DocumentTest
438438
```
439439
440440
[(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
@@ -49,3 +49,4 @@ 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_53 } from './mandatoryTests/mandatoryTest_6_1_53.js'
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
first_known_exploitation_dates: {
39+
elements: {
40+
additionalProperties: true,
41+
optionalProperties: {
42+
date: {type: 'string'},
43+
exploitation_date: {type: 'string'},
44+
},
45+
},
46+
},
47+
},
48+
},
49+
},
50+
},
51+
})
52+
53+
const validate = ajv.compile(inputSchema)
54+
55+
/**
56+
* This implements the mandatory test 6.1.53 of the CSAF 2.1 standard.
57+
*
58+
* @param {any} doc
59+
*/
60+
export function mandatoryTest_6_1_53(doc) {
61+
/*
62+
The `ctx` variable holds the state that is accumulated during the test ran and is
63+
finally returned by the function.
64+
*/
65+
const ctx = {
66+
errors:
67+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
68+
isValid: true,
69+
}
70+
71+
if (!validate(doc)) {
72+
return ctx
73+
}
74+
const status = doc.document.tracking.status
75+
if (status !== 'final' && status !== 'interim') {
76+
return ctx
77+
}
78+
79+
doc.vulnerabilities?.forEach((vulnerability, vulnerabilityIndex) => {
80+
const exploitDate = vulnerability.first_known_exploitation_dates || []
81+
exploitDate.forEach((exploit, exploitIdx) => {
82+
const date = exploit.date
83+
const exploitationDate = exploit.exploitation_date
84+
85+
if (
86+
compareZonedDateTimes(
87+
/** @type {string} */ (date),
88+
/** @type {string} */ (exploitationDate)
89+
) < 0
90+
) {
91+
ctx.isValid = false
92+
ctx.errors.push({
93+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/first_known_exploitation_dates/${exploitIdx}`,
94+
message: `the status is ${status}, but the 'exploitation_date' are newer than the 'date'`,
95+
})
96+
}
97+
})
98+
})
99+
100+
return ctx
101+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import assert from 'node:assert/strict'
2+
import { mandatoryTest_6_1_53 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_53.js'
3+
4+
describe('mandatoryTest_6_1_53', function () {
5+
it('only runs on relevant documents', function () {
6+
assert.equal(mandatoryTest_6_1_53({ document: 'mydoc' }).isValid, true)
7+
})
8+
9+
it('skips status draft', function () {
10+
assert.equal(
11+
mandatoryTest_6_1_53({
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 vulnerability', function () {
25+
assert.equal(
26+
mandatoryTest_6_1_53({
27+
document: {
28+
tracking: {
29+
revision_history: [],
30+
status: 'final',
31+
},
32+
},
33+
vulnerabilities: [
34+
{}, // should be ignored
35+
{
36+
first_known_exploitation_dates: [
37+
{
38+
date: '2024-01-24T12:34:56.789Z',
39+
exploitation_date: '2024-01-24T13:00:00.000Z',
40+
},
41+
],
42+
},
43+
],
44+
}).isValid,
45+
false
46+
)
47+
})
48+
49+
it('skips empty first_known_exploitation_date', function () {
50+
assert.equal(
51+
mandatoryTest_6_1_53({
52+
document: {
53+
tracking: {
54+
revision_history: [],
55+
status: 'final',
56+
},
57+
},
58+
vulnerabilities: [
59+
{
60+
first_known_exploitation_dates: [
61+
{}, // should be ignored
62+
{
63+
date: '2024-01-24T12:34:56.789Z',
64+
exploitation_date: '2024-01-24T13:00:00.000Z',
65+
},
66+
],
67+
},
68+
],
69+
}).isValid,
70+
false
71+
)
72+
})
73+
})

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ const excluded = [
3838
'6.1.50',
3939
'6.1.51',
4040
'6.1.52',
41-
'6.1.53',
4241
'6.1.54',
4342
'6.1.55',
4443
'6.2.11',

0 commit comments

Comments
 (0)