Skip to content

Commit 6e2629e

Browse files
feat: add mandatory test 6.1.46
1 parent 8bd8ce4 commit 6e2629e

File tree

7 files changed

+291
-1
lines changed

7 files changed

+291
-1
lines changed

csaf_2_1/mandatoryTests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ export { mandatoryTest_6_1_38 } from './mandatoryTests/mandatoryTests_6_1_38.js'
4747
export { mandatoryTest_6_1_39 } from './mandatoryTests/mandatoryTest_6_1_39.js'
4848
export { mandatoryTest_6_1_40 } from './mandatoryTests/mandatoryTest_6_1_40.js'
4949
export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
50+
export { mandatoryTest_6_1_46 } from './mandatoryTests/mandatoryTest_6_1_46.js'
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
import csafAjv from '../../lib/shared/csafAjv.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+
vulnerabilities: {
16+
elements: {
17+
additionalProperties: true,
18+
optionalProperties: {
19+
metrics: {
20+
elements: {
21+
additionalProperties: true,
22+
optionalProperties: {
23+
content: {
24+
additionalProperties: true,
25+
properties: {
26+
ssvc_v1: {
27+
additionalProperties: true,
28+
properties: {},
29+
},
30+
},
31+
},
32+
},
33+
},
34+
},
35+
},
36+
},
37+
},
38+
},
39+
})
40+
41+
const validateInput = ajv.compile(inputSchema)
42+
43+
const validate_ssvc_v1 = csafAjv.compile({
44+
$ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point_Value_Selection-1-0-1.schema.json',
45+
})
46+
47+
/**
48+
* This implements the mandatory test 6.1.46 of the CSAF 2.1 standard.
49+
*
50+
* @param {unknown} doc
51+
*/
52+
export function mandatoryTest_6_1_46(doc) {
53+
/*
54+
The `ctx` variable holds the state that is accumulated during the test ran and is
55+
finally returned by the function.
56+
*/
57+
const ctx = {
58+
errors:
59+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
60+
isValid: true,
61+
}
62+
63+
if (!validateInput(doc)) {
64+
return ctx
65+
}
66+
67+
doc.vulnerabilities?.forEach((vulnerability, vulnerabilityIndex) => {
68+
vulnerability.metrics?.forEach((metric, metricIndex) => {
69+
const valid = validate_ssvc_v1(metric.content?.ssvc_v1)
70+
if (!valid) {
71+
ctx.isValid = false
72+
for (const err of validate_ssvc_v1.errors ?? []) {
73+
ctx.errors.push({
74+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v1${err.instancePath}`,
75+
message: err.message ?? '',
76+
})
77+
}
78+
}
79+
})
80+
})
81+
82+
return ctx
83+
}

lib/shared/csafAjv.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@ import cvss_v2_0 from './csafAjv/cvss-v2.0.js'
44
import cvss_v3_0 from './csafAjv/cvss-v3.0.js'
55
import cvss_v3_1 from './csafAjv/cvss-v3.1.js'
66
import cvss_v4_0 from './csafAjv/cvss-v4.0.js'
7+
import ssvc_v1 from './csafAjv/ssvc-v1.js'
8+
import ssvc_v1_schemaVersion from './csafAjv/ssvc-v1_schemaVersion.js'
79

810
const csafAjv = new Ajv2020({ strict: false, allErrors: true })
911
addFormats(csafAjv)
1012
csafAjv.addSchema(cvss_v2_0, 'https://www.first.org/cvss/cvss-v2.0.json')
1113
csafAjv.addSchema(cvss_v3_0, 'https://www.first.org/cvss/cvss-v3.0.json')
1214
csafAjv.addSchema(cvss_v3_1, 'https://www.first.org/cvss/cvss-v3.1.json')
1315
csafAjv.addSchema(cvss_v4_0, 'https://www.first.org/cvss/cvss-v4.0.json')
16+
csafAjv.addSchema(
17+
ssvc_v1,
18+
'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point_Value_Selection-1-0-1.schema.json'
19+
)
20+
csafAjv.addSchema(
21+
ssvc_v1_schemaVersion,
22+
'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json'
23+
)
1424

1525
export default csafAjv

lib/shared/csafAjv/ssvc-v1.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
export default {
2+
$schema: 'https://json-schema.org/draft/2020-12/schema',
3+
$id: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point_Value_Selection-1-0-1.schema.json',
4+
description:
5+
'This schema defines the structure for selecting SSVC Decision Points and their evaluated values for a given vulnerability. Each vulnerability can have multiple Decision Points, and each Decision Point can have multiple selected values when full certainty is not available.',
6+
$defs: {
7+
id: {
8+
type: 'string',
9+
description:
10+
'Identifier for the vulnerability that was evaluation, such as CVE, CERT/CC VU#, OSV id, Bugtraq, GHSA etc.',
11+
examples: ['CVE-1900-1234', 'VU#11111', 'GHSA-11a1-22b2-33c3'],
12+
minLength: 1,
13+
},
14+
role: {
15+
type: 'string',
16+
description:
17+
'The role of the stakeholder performing the evaluation (e.g., Supplier, Deployer, Coordinator). See SSVC documentation for a currently identified list: https://certcc.github.io/SSVC/topics/enumerating_stakeholders/',
18+
examples: ['Supplier', 'Deployer', 'Coordinator'],
19+
minLength: 1,
20+
},
21+
timestamp: {
22+
description:
23+
'Date and time when the evaluation of the Vulnerability was performed according to RFC 3339, section 5.6.',
24+
type: 'string',
25+
format: 'date-time',
26+
},
27+
SsvcdecisionpointselectionSchema: {
28+
description:
29+
'A down-selection of SSVC Decision Points that represent an evaluation at a specific time of a Vulnerability evaluation.',
30+
properties: {
31+
name: {
32+
$ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/decision_point/properties/name',
33+
},
34+
namespace: {
35+
$ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/decision_point/properties/namespace',
36+
},
37+
values: {
38+
description:
39+
'One or more Decision Point Values that were selected for this Decision Point. If the evaluation is uncertain, multiple values may be listed to reflect the potential range of possibilities.',
40+
title: 'values',
41+
type: 'array',
42+
minItems: 1,
43+
items: {
44+
$ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/decision_point_value/properties/name',
45+
},
46+
},
47+
version: {
48+
$ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/decision_point/properties/version',
49+
},
50+
},
51+
type: 'object',
52+
required: ['name', 'namespace', 'values', 'version'],
53+
additionalProperties: false,
54+
},
55+
},
56+
properties: {
57+
id: {
58+
$ref: '#/$defs/id',
59+
},
60+
role: {
61+
$ref: '#/$defs/role',
62+
},
63+
schemaVersion: {
64+
$ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/schemaVersion',
65+
},
66+
timestamp: {
67+
$ref: '#/$defs/timestamp',
68+
},
69+
selections: {
70+
description:
71+
'An array of Decision Points and their selected values for the identified Vulnerability. If a clear evaluation is uncertain, multiple values may be listed for a Decision Point instead of waiting for perfect clarity.',
72+
title: 'selections',
73+
type: 'array',
74+
minItems: 1,
75+
items: {
76+
$ref: '#/$defs/SsvcdecisionpointselectionSchema',
77+
},
78+
},
79+
},
80+
type: 'object',
81+
required: ['selections', 'id', 'timestamp', 'schemaVersion'],
82+
additionalProperties: false,
83+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
export default {
2+
$schema: 'https://json-schema.org/draft/2020-12/schema',
3+
title: 'Decision Point schema definition',
4+
$id: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json',
5+
description:
6+
'Decision points are the basic building blocks of SSVC decision functions. Individual decision points describe a single aspect of the input to a decision function.',
7+
$defs: {
8+
schemaVersion: {
9+
description: 'Schema version used to represent this Decision Point.',
10+
type: 'string',
11+
enum: ['1-0-1'],
12+
},
13+
decision_point_value: {
14+
type: 'object',
15+
additionalProperties: false,
16+
properties: {
17+
key: {
18+
type: 'string',
19+
description:
20+
'A short, unique string (or key) used as a shorthand identifier for a Decision Point Value.',
21+
minLength: 1,
22+
examples: ['P', 'Y'],
23+
},
24+
name: {
25+
type: 'string',
26+
description: 'A short label that identifies a Decision Point Value',
27+
minLength: 1,
28+
examples: ['Public PoC', 'Yes'],
29+
},
30+
description: {
31+
type: 'string',
32+
description: 'A full description of the Decision Point Value.',
33+
minLength: 1,
34+
examples: [
35+
'One of the following is true: (1) Typical public PoC exists in sources such as Metasploit or websites like ExploitDB; or (2) the vulnerability has a well-known method of exploitation.',
36+
'Attackers can reliably automate steps 1-4 of the kill chain.',
37+
],
38+
},
39+
},
40+
required: ['key', 'name', 'description'],
41+
},
42+
decision_point: {
43+
type: 'object',
44+
additionalProperties: false,
45+
properties: {
46+
schemaVersion: {
47+
$ref: '#/$defs/schemaVersion',
48+
},
49+
namespace: {
50+
type: 'string',
51+
description:
52+
'Namespace (a short, unique string): The value must be one of the official namespaces, currenlty "ssvc", "cvss" OR can start with \'x_\' for private namespaces. See SSVC Documentation for details.',
53+
pattern: '^(?=.{3,100}$)(x_)?[a-z0-9]{3}([/.-]?[a-z0-9]+){0,97}$',
54+
examples: ['ssvc', 'cvss', 'x_custom', 'x_custom/extension'],
55+
},
56+
version: {
57+
type: 'string',
58+
description:
59+
'Version (a semantic version string) that identifies the version of a Decision Point.',
60+
pattern:
61+
'^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$',
62+
examples: ['1.0.1', '1.0.1-alpha'],
63+
},
64+
key: {
65+
type: 'string',
66+
description:
67+
'A short, unique string (or key) used as a shorthand identifier for a Decision Point.',
68+
69+
minLength: 1,
70+
examples: ['E', 'A'],
71+
},
72+
name: {
73+
type: 'string',
74+
description: 'A short label that identifies a Decision Point.',
75+
minLength: 1,
76+
examples: ['Exploitation', 'Automatable'],
77+
},
78+
description: {
79+
type: 'string',
80+
description:
81+
'A full description of the Decision Point, explaining what it represents and how it is used in SSVC.',
82+
minLength: 1,
83+
},
84+
values: {
85+
description: 'A set of possible answers for a given Decision Point',
86+
uniqueItems: true,
87+
type: 'array',
88+
minItems: 1,
89+
items: {
90+
$ref: '#/$defs/decision_point_value',
91+
},
92+
},
93+
},
94+
required: [
95+
'namespace',
96+
'version',
97+
'key',
98+
'name',
99+
'description',
100+
'values',
101+
'schemaVersion',
102+
],
103+
},
104+
},
105+
$ref: '#/$defs/decision_point',
106+
}
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_46 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_46.js'
3+
4+
describe('mandatoryTest_6_1_46', function () {
5+
it('only runs on relevant documents', function () {
6+
assert.equal(mandatoryTest_6_1_46({ 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
@@ -33,7 +33,6 @@ const excluded = [
3333
'6.1.43',
3434
'6.1.44',
3535
'6.1.45',
36-
'6.1.46',
3736
'6.1.47',
3837
'6.1.48',
3938
'6.1.49',

0 commit comments

Comments
 (0)