Skip to content

Commit fcf6c66

Browse files
committed
feat: add additional CSAF 2.0 validation
The following fields are further validated: - document.tracking - document.distribution - document.publisher - product_tree (root) Signed-off-by: Rifa Achrinza <[email protected]>
1 parent 813cfc9 commit fcf6c66

File tree

1 file changed

+172
-1
lines changed

1 file changed

+172
-1
lines changed

scripts/advisories/validate-csaf20.ts

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,18 @@ glob(path.resolve(__dirname, csaf20DocumentGlob), async (err, matches) => {
2121
` L Validating: ${path.relative(process.cwd(), filePath)}...`,
2222
);
2323
const fileContents = require(filePath);
24-
const {isValid, errors} = await document.validate({document: fileContents});
24+
const validationResults: Record<string, ValidationResult> = {
25+
secvisogram: await document.validate({document: fileContents}),
26+
revisionDate: validateTracking(fileContents),
27+
distribution: validateDistribution(fileContents),
28+
productTree: validateProductTree(fileContents),
29+
publisher: validatePublisher(fileContents),
30+
};
31+
32+
const validationResultsValues = Object.values(validationResults);
33+
34+
const errors = validationResultsValues.flatMap(x => x.errors);
35+
const isValid = errors.length < 1;
2536

2637
if (isValid) console.log('Done!');
2738
else {
@@ -44,3 +55,163 @@ glob(path.resolve(__dirname, csaf20DocumentGlob), async (err, matches) => {
4455

4556
console.log('CSAF 2.0 validation done.');
4657
});
58+
59+
interface ValidationResult {
60+
isValid: boolean;
61+
errors: {
62+
instancePath: string;
63+
message?: string;
64+
}[];
65+
}
66+
67+
function validateTracking(fileContents: any): ValidationResult {
68+
const tracking = fileContents.document.tracking;
69+
let errors: ValidationResult['errors'] = [];
70+
71+
if (!/^(lbsa-[1-9][0-9]*)$/.test(tracking.id)) {
72+
errors.push({
73+
instancePath: 'document/tracking/id',
74+
message: 'id must match `/^(lbsa-[1-9][0-9]*)$/`.',
75+
});
76+
}
77+
78+
if (tracking.status !== 'final') {
79+
errors.push({
80+
instancePath: 'document/tracking/status',
81+
message: 'status must equal `final`.',
82+
});
83+
}
84+
85+
if (tracking.revision_history[0].date != tracking.current_release_date) {
86+
errors.push({
87+
instancePath: 'document/tracking/current_release_date',
88+
message: 'current_release_date does not match latest revision history.',
89+
});
90+
}
91+
92+
if (
93+
tracking.revision_history[tracking.revision_history.length - 1].date !=
94+
tracking.initial_release_date
95+
) {
96+
errors.push({
97+
instancePath: 'document/tracking/initial_release_date',
98+
message: 'initial_release_date does not match first revision history.',
99+
});
100+
}
101+
102+
function getVersioningSystem(str: string): 'integer' | 'semver' {
103+
const intVersioningRegex = /^(0|[1-9][0-9]*)$/;
104+
return intVersioningRegex.test(str) ? 'integer' : 'semver';
105+
}
106+
107+
if (tracking.revision_history.length > 1) {
108+
const versioningSystem = getVersioningSystem(
109+
tracking.revision_history[0].number,
110+
);
111+
112+
for (let i = 1; i < tracking.revision_history.length; i++) {
113+
if (
114+
getVersioningSystem(tracking.revision_history[i].number) !==
115+
versioningSystem
116+
) {
117+
errors.push({
118+
instancePath: `document/revision_history/${i}/number`,
119+
message: 'number version system inconsistent.',
120+
});
121+
}
122+
}
123+
}
124+
125+
return {
126+
isValid: errors.length < 1,
127+
errors,
128+
};
129+
}
130+
131+
function validateDistribution(fileContents: any): ValidationResult {
132+
const distribution = fileContents.document.distribution;
133+
const errors: ValidationResult['errors'] = [];
134+
const standardisedDistributionInfo =
135+
'Disclosure is not limited.\n' +
136+
'SPDX-FileCopyrightText: LoopBack Contributors\n' +
137+
'SPDX-License-Identifier: MIT';
138+
139+
if ((distribution.text as string) !== standardisedDistributionInfo) {
140+
errors.push({
141+
instancePath: 'document/distribution/text',
142+
message: `text must be \`${standardisedDistributionInfo}\``,
143+
});
144+
}
145+
146+
if (distribution.tlp?.label !== 'WHITE') {
147+
errors.push({
148+
instancePath: 'document/distribution/tlp/label',
149+
message: 'label must be `WHITE`',
150+
});
151+
}
152+
153+
return {
154+
isValid: errors.length < 1,
155+
errors,
156+
};
157+
}
158+
159+
function validateProductTree(fileContents: any): ValidationResult {
160+
const productTree = fileContents.product_tree.branches;
161+
const errors: ValidationResult['errors'] = [];
162+
const lbRootBranchIndex = (productTree as any[])?.findIndex(
163+
v => v.name === 'The LoopBack Maintainers',
164+
);
165+
166+
if (lbRootBranchIndex > -1) {
167+
const lbRootBranch = productTree[lbRootBranchIndex];
168+
if (lbRootBranch.category !== 'vendor') {
169+
errors.push({
170+
instancePath: `product_tree/branches/${lbRootBranchIndex}/category`,
171+
message:
172+
'category must be `vendor` for `The LoopBack Maintainers` vendor root branch.',
173+
});
174+
}
175+
} else {
176+
errors.push({
177+
instancePath: 'product_tree/branches',
178+
message: '`The LoopBack Maintainers` vendor root branch must exist.',
179+
});
180+
}
181+
182+
return {
183+
isValid: errors.length < 1,
184+
errors,
185+
};
186+
}
187+
188+
function validatePublisher(fileContents: any): ValidationResult {
189+
const publisher = fileContents.document.publisher;
190+
const errors: ValidationResult['errors'] = [];
191+
192+
if (publisher.category !== 'vendor') {
193+
errors.push({
194+
instancePath: 'document/publisher/category',
195+
message: 'category must equal `vendor`',
196+
});
197+
}
198+
199+
if (publisher.name !== 'The LoopBack Maintainers') {
200+
errors.push({
201+
instancePath: 'document/publisher/name',
202+
message: 'name must equal `The LoopBack Maintainers`',
203+
});
204+
}
205+
206+
if (publisher.namespace !== 'https://loopback.io') {
207+
errors.push({
208+
instancePath: 'document/publisher/namespace',
209+
message: 'namespace must equal `https://loopback.io`',
210+
});
211+
}
212+
213+
return {
214+
isValid: errors.length < 1,
215+
errors,
216+
};
217+
}

0 commit comments

Comments
 (0)