Skip to content

Commit 633010c

Browse files
committed
feat(CSAF2.1): add recommendedTest_6_2_11.js
1 parent c5c978b commit 633010c

File tree

4 files changed

+180
-5
lines changed

4 files changed

+180
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,6 @@ The following tests are not yet implemented and therefore missing:
338338
339339
**Recommended Tests**
340340
341-
- Recommended Test 6.2.11
342341
- Recommended Test 6.2.19
343342
- Recommended Test 6.2.20
344343
- Recommended Test 6.2.21
@@ -454,6 +453,7 @@ export const recommendedTest_6_2_7: DocumentTest
454453
export const recommendedTest_6_2_8: DocumentTest
455454
export const recommendedTest_6_2_9: DocumentTest
456455
export const recommendedTest_6_2_10: DocumentTest
456+
export const recommendedTest_6_2_11: DocumentTest
457457
export const recommendedTest_6_2_12: DocumentTest
458458
export const recommendedTest_6_2_13: DocumentTest
459459
export const recommendedTest_6_2_14: DocumentTest
Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,104 @@
1-
import { optionalTest_6_2_11 } from '../../optionalTests.js'
1+
import Ajv from 'ajv/dist/jtd.js'
2+
3+
const ajv = new Ajv()
4+
5+
const inputSchema = /** @type {const} */ ({
6+
additionalProperties: true,
7+
properties: {
8+
document: {
9+
additionalProperties: true,
10+
properties: {
11+
references: {
12+
elements: {
13+
additionalProperties: true,
14+
optionalProperties: {
15+
category: { type: 'string' },
16+
url: { type: 'string' },
17+
},
18+
},
19+
},
20+
tracking: {
21+
additionalProperties: true,
22+
optionalProperties: {
23+
id: { type: 'string' },
24+
},
25+
},
26+
},
27+
},
28+
},
29+
})
30+
const validateInput = ajv.compile(inputSchema)
231

332
/**
4-
* @param {unknown} doc
33+
* Test for the optional test 6.2.11
34+
* @param {any} doc
535
*/
636
export function recommendedTest_6_2_11(doc) {
7-
return optionalTest_6_2_11(doc)
37+
const ctx = {
38+
warnings:
39+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
40+
}
41+
42+
if (!validateInput(doc)) {
43+
return ctx
44+
}
45+
46+
const trackingId = doc.document.tracking.id
47+
if (!trackingId) {
48+
return ctx
49+
}
50+
51+
const filename = transformTrackingIdToFilename(trackingId)
52+
53+
const selfReferences =
54+
doc.document.references
55+
?.map((reference, index) => ({ ...reference, index }))
56+
.filter((ref) => ref.category === 'self') ?? []
57+
58+
if (selfReferences.length === 0) {
59+
return ctx
60+
}
61+
62+
const hasValidSelfReference = selfReferences.some((reference) => {
63+
const url = reference.url || ''
64+
return (
65+
typeof url === 'string' &&
66+
url.startsWith('https://') &&
67+
url.endsWith(filename)
68+
)
69+
})
70+
71+
if (hasValidSelfReference) {
72+
return ctx
73+
}
74+
75+
selfReferences.forEach((reference) => {
76+
const url = reference.url || ''
77+
const referenceIndex = reference.index
78+
79+
if (
80+
typeof url !== 'string' ||
81+
!url.startsWith('https://') ||
82+
!url.endsWith(filename)
83+
) {
84+
ctx.warnings.push({
85+
instancePath: `/document/references/${referenceIndex}/url`,
86+
message:
87+
'The reference category is "self", but the URL does not fulfill the requirements of a valid filename for a CSAF document.',
88+
})
89+
}
90+
})
91+
92+
return ctx
93+
}
94+
95+
/** * Transforms a tracking ID into a filename.
96+
*
97+
* @param {string} trackingId
98+
* @returns {string} filename
99+
*/
100+
function transformTrackingIdToFilename(trackingId) {
101+
const lowerCase = trackingId.toLowerCase()
102+
const replacedString = lowerCase.replace(/[^+\-a-z0-9]+/g, '_')
103+
return replacedString + '.json'
8104
}

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ const excluded = [
3939
'6.1.53',
4040
'6.1.54',
4141
'6.1.55',
42-
'6.2.11',
4342
'6.2.19',
4443
'6.2.20',
4544
'6.2.21',
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import assert from 'node:assert'
2+
import { recommendedTest_6_2_11 } from '../../csaf_2_1/recommendedTests.js'
3+
4+
describe('recommendedTest_6_2_11', function () {
5+
it('only runs on relevant documents', function () {
6+
assert.equal(
7+
recommendedTest_6_2_11({ vulnerabilities: 'mydoc' }).warnings.length,
8+
0
9+
)
10+
})
11+
12+
it('skips documents with empty tracking id', function () {
13+
assert.equal(
14+
recommendedTest_6_2_11({
15+
document: {
16+
references: [
17+
{
18+
category: 'self',
19+
url: 'https://example.com/security/data/csaf/2024/oasis_csaf_tc-csaf_2.1-2024-6-2-11-01_1.json',
20+
},
21+
],
22+
tracking: { id: '' },
23+
},
24+
}).warnings.length,
25+
0
26+
)
27+
})
28+
29+
it('skips reference without self references', function () {
30+
assert.equal(
31+
recommendedTest_6_2_11({
32+
document: {
33+
references: [
34+
{
35+
category: 'external',
36+
url: 'https://example.com/security/data/csaf/2024/oasis_csaf_tc-csaf_2.1-2024-6-2-11-01_1.json',
37+
},
38+
],
39+
tracking: { id: 'OASIS_CSAF_TC-CSAF_2.1-2024-6-2-11-01' },
40+
},
41+
}).warnings.length,
42+
0
43+
)
44+
})
45+
46+
it('should warn when self reference has empty url', function () {
47+
assert.equal(
48+
recommendedTest_6_2_11({
49+
document: {
50+
references: [
51+
{
52+
category: 'self',
53+
url: '',
54+
},
55+
],
56+
tracking: { id: 'OASIS_CSAF_TC-CSAF_2.1-2024-6-2-11-01' },
57+
},
58+
}).warnings.length,
59+
1
60+
)
61+
})
62+
63+
it('skips empty reference', function () {
64+
assert.equal(
65+
recommendedTest_6_2_11({
66+
document: {
67+
references: [
68+
{},
69+
{
70+
category: 'self',
71+
url: 'https://example.com/security/data/csaf/2024/oasis_csaf_tc-csaf_2.1-2024-6-2-11-01_1.json',
72+
},
73+
],
74+
tracking: { id: 'OASIS_CSAF_TC-CSAF_2.1-2024-6-2-11-01' },
75+
},
76+
}).warnings.length,
77+
1
78+
)
79+
})
80+
})

0 commit comments

Comments
 (0)