@cyclonedx/cdxgen 12.1.4 → 12.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -39
- package/bin/cdxgen.js +181 -90
- package/bin/evinse.js +4 -4
- package/bin/repl.js +3 -3
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +484 -440
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +5 -18
- package/lib/evinser/swiftsem.js +1 -1
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/caxa.js +1 -1
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +429 -14
- package/lib/helpers/envcontext.js +23 -8
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +305 -0
- package/lib/helpers/pythonutils.poku.js +469 -0
- package/lib/helpers/utils.js +970 -528
- package/lib/helpers/utils.poku.js +139 -256
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +4 -10
- package/lib/parsers/npmrc.js +92 -0
- package/lib/parsers/npmrc.poku.js +528 -0
- package/lib/server/openapi.yaml +1 -10
- package/lib/server/server.js +58 -16
- package/lib/server/server.poku.js +123 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +197 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
- package/lib/third-party/arborist/lib/node.js +3 -3
- package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
- package/lib/third-party/arborist/lib/tree-check.js +1 -1
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -8
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +18 -0
- package/types/lib/helpers/pythonutils.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +532 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +26 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +21 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
__test,
|
|
5
|
+
getAllComplianceRules,
|
|
6
|
+
getCraRules,
|
|
7
|
+
getScvsRules,
|
|
8
|
+
} from "./complianceRules.js";
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
componentLicenseId,
|
|
12
|
+
inventoryComponents,
|
|
13
|
+
looksLikeSpdx,
|
|
14
|
+
collectReferencedRefs,
|
|
15
|
+
} = __test;
|
|
16
|
+
|
|
17
|
+
function baseBom(overrides = {}) {
|
|
18
|
+
return {
|
|
19
|
+
bomFormat: "CycloneDX",
|
|
20
|
+
specVersion: "1.6",
|
|
21
|
+
serialNumber: "urn:uuid:1b671687-395b-41f5-a30f-a58921a69b79",
|
|
22
|
+
metadata: {
|
|
23
|
+
timestamp: "2024-01-02T03:04:05Z",
|
|
24
|
+
tools: {
|
|
25
|
+
components: [
|
|
26
|
+
{ type: "application", name: "cdxgen", version: "12.0.0" },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
component: {
|
|
30
|
+
name: "demo",
|
|
31
|
+
version: "1.0.0",
|
|
32
|
+
type: "application",
|
|
33
|
+
"bom-ref": "pkg:generic/demo@1.0.0",
|
|
34
|
+
},
|
|
35
|
+
supplier: {
|
|
36
|
+
name: "Acme",
|
|
37
|
+
contact: [{ email: "psirt@example.com" }],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
components: [
|
|
41
|
+
{
|
|
42
|
+
type: "library",
|
|
43
|
+
name: "lodash",
|
|
44
|
+
version: "4.17.21",
|
|
45
|
+
purl: "pkg:npm/lodash@4.17.21",
|
|
46
|
+
"bom-ref": "pkg:npm/lodash@4.17.21",
|
|
47
|
+
licenses: [{ license: { id: "MIT" } }],
|
|
48
|
+
hashes: [{ alg: "SHA-256", content: "abc" }],
|
|
49
|
+
copyright: "Copyright (c) OpenJS",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
dependencies: [
|
|
53
|
+
{ ref: "pkg:generic/demo@1.0.0", dependsOn: ["pkg:npm/lodash@4.17.21"] },
|
|
54
|
+
{ ref: "pkg:npm/lodash@4.17.21", dependsOn: [] },
|
|
55
|
+
],
|
|
56
|
+
...overrides,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("complianceRules catalog", () => {
|
|
61
|
+
it("exposes SCVS + CRA rules with stable ids", () => {
|
|
62
|
+
const all = getAllComplianceRules();
|
|
63
|
+
assert.ok(all.length >= 80, `expected >= 80 rules, got ${all.length}`);
|
|
64
|
+
const scvs = getScvsRules();
|
|
65
|
+
const cra = getCraRules();
|
|
66
|
+
assert.strictEqual(scvs.length + cra.length, all.length);
|
|
67
|
+
for (const r of all) {
|
|
68
|
+
assert.ok(typeof r.id === "string" && r.id.length > 0, `bad id ${r.id}`);
|
|
69
|
+
assert.ok(
|
|
70
|
+
typeof r.evaluate === "function",
|
|
71
|
+
`rule ${r.id} missing evaluate`,
|
|
72
|
+
);
|
|
73
|
+
assert.ok(
|
|
74
|
+
Array.isArray(r.standardRefs),
|
|
75
|
+
`rule ${r.id} missing standardRefs`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const ids = new Set(all.map((r) => r.id));
|
|
79
|
+
assert.strictEqual(ids.size, all.length, "rule ids must be unique");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("SCVS rule ids match the SCVS-X.Y convention", () => {
|
|
83
|
+
for (const r of getScvsRules()) {
|
|
84
|
+
assert.match(r.id, /^SCVS-\d+\.\d+$/);
|
|
85
|
+
assert.strictEqual(r.standard, "SCVS");
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("CRA rule ids match the CRA-MIN-XXX convention", () => {
|
|
90
|
+
for (const r of getCraRules()) {
|
|
91
|
+
assert.match(r.id, /^CRA-MIN-\d+$/);
|
|
92
|
+
assert.strictEqual(r.standard, "CRA");
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("complianceRules helpers", () => {
|
|
98
|
+
it("inventoryComponents filters non-inventory types", () => {
|
|
99
|
+
const bom = {
|
|
100
|
+
components: [
|
|
101
|
+
{ type: "library", name: "a" },
|
|
102
|
+
{ type: "cryptographic-asset", name: "b" },
|
|
103
|
+
{ type: "framework", name: "c" },
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
assert.deepStrictEqual(
|
|
107
|
+
inventoryComponents(bom).map((c) => c.name),
|
|
108
|
+
["a", "c"],
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("looksLikeSpdx accepts common forms and rejects garbage", () => {
|
|
113
|
+
assert.ok(looksLikeSpdx("MIT"));
|
|
114
|
+
assert.ok(looksLikeSpdx("Apache-2.0"));
|
|
115
|
+
assert.ok(looksLikeSpdx("Apache-2.0 OR MIT"));
|
|
116
|
+
assert.ok(looksLikeSpdx("(MIT AND LGPL-2.1-only)"));
|
|
117
|
+
assert.ok(!looksLikeSpdx("NOASSERTION"));
|
|
118
|
+
assert.ok(!looksLikeSpdx("unknown"));
|
|
119
|
+
assert.ok(!looksLikeSpdx(""));
|
|
120
|
+
assert.ok(!looksLikeSpdx(null));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("componentLicenseId prefers id then name then expression", () => {
|
|
124
|
+
assert.strictEqual(
|
|
125
|
+
componentLicenseId({ licenses: [{ license: { id: "MIT" } }] }),
|
|
126
|
+
"MIT",
|
|
127
|
+
);
|
|
128
|
+
assert.strictEqual(
|
|
129
|
+
componentLicenseId({ licenses: [{ license: { name: "Custom" } }] }),
|
|
130
|
+
"Custom",
|
|
131
|
+
);
|
|
132
|
+
assert.strictEqual(
|
|
133
|
+
componentLicenseId({ licenses: [{ expression: "MIT OR Apache-2.0" }] }),
|
|
134
|
+
"MIT OR Apache-2.0",
|
|
135
|
+
);
|
|
136
|
+
assert.strictEqual(componentLicenseId({}), null);
|
|
137
|
+
assert.strictEqual(componentLicenseId({ licenses: [] }), null);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("collectReferencedRefs gathers all refs", () => {
|
|
141
|
+
const bom = baseBom();
|
|
142
|
+
const refs = collectReferencedRefs(bom);
|
|
143
|
+
assert.ok(refs.has("pkg:generic/demo@1.0.0"));
|
|
144
|
+
assert.ok(refs.has("pkg:npm/lodash@4.17.21"));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("SCVS automatable rules on a clean BOM", () => {
|
|
149
|
+
const bom = baseBom();
|
|
150
|
+
const rules = getScvsRules().filter((r) => r.automatable);
|
|
151
|
+
|
|
152
|
+
it("SCVS-1.1, 1.3, 1.7, 2.1, 2.3, 2.7, 2.9, 2.11, 2.12, 2.14, 3.20 all pass", () => {
|
|
153
|
+
const expected = [
|
|
154
|
+
"SCVS-1.1",
|
|
155
|
+
"SCVS-1.3",
|
|
156
|
+
"SCVS-1.7",
|
|
157
|
+
"SCVS-2.1",
|
|
158
|
+
"SCVS-2.3",
|
|
159
|
+
"SCVS-2.7",
|
|
160
|
+
"SCVS-2.9",
|
|
161
|
+
"SCVS-2.11",
|
|
162
|
+
"SCVS-2.12",
|
|
163
|
+
"SCVS-2.14",
|
|
164
|
+
"SCVS-3.20",
|
|
165
|
+
];
|
|
166
|
+
for (const id of expected) {
|
|
167
|
+
const r = rules.find((x) => x.id === id);
|
|
168
|
+
assert.ok(r, `missing rule ${id}`);
|
|
169
|
+
const res = r.evaluate(bom);
|
|
170
|
+
assert.strictEqual(res.status, "pass", `${id}: ${res.message}`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("SCVS-2.4 fails when BOM is not signed, passes when signed", () => {
|
|
175
|
+
const rule = rules.find((r) => r.id === "SCVS-2.4");
|
|
176
|
+
assert.strictEqual(rule.evaluate(bom).status, "fail");
|
|
177
|
+
const signed = baseBom({
|
|
178
|
+
signature: { algorithm: "RS512", value: "xxx" },
|
|
179
|
+
});
|
|
180
|
+
assert.strictEqual(rule.evaluate(signed).status, "pass");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("SCVS-1.1 fails when a component has no version", () => {
|
|
184
|
+
const rule = rules.find((r) => r.id === "SCVS-1.1");
|
|
185
|
+
const bad = baseBom({
|
|
186
|
+
components: [{ type: "library", name: "no-version", purl: "pkg:npm/x" }],
|
|
187
|
+
});
|
|
188
|
+
const res = rule.evaluate(bad);
|
|
189
|
+
assert.strictEqual(res.status, "fail");
|
|
190
|
+
assert.match(res.message, /missing a version/);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("SCVS-2.3 fails when serialNumber is missing", () => {
|
|
194
|
+
const rule = rules.find((r) => r.id === "SCVS-2.3");
|
|
195
|
+
assert.strictEqual(
|
|
196
|
+
rule.evaluate(baseBom({ serialNumber: undefined })).status,
|
|
197
|
+
"fail",
|
|
198
|
+
);
|
|
199
|
+
assert.strictEqual(
|
|
200
|
+
rule.evaluate(baseBom({ serialNumber: "garbage" })).status,
|
|
201
|
+
"fail",
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("SCVS-2.11 fails when root name or version is missing", () => {
|
|
206
|
+
const rule = rules.find((r) => r.id === "SCVS-2.11");
|
|
207
|
+
const noVer = baseBom({
|
|
208
|
+
metadata: {
|
|
209
|
+
...baseBom().metadata,
|
|
210
|
+
component: { name: "x", "bom-ref": "x", type: "application" },
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
assert.strictEqual(rule.evaluate(noVer).status, "fail");
|
|
214
|
+
const noName = baseBom({
|
|
215
|
+
metadata: {
|
|
216
|
+
...baseBom().metadata,
|
|
217
|
+
component: {},
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
assert.strictEqual(rule.evaluate(noName).status, "fail");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("SCVS-2.12 fails when a purl is unparseable", () => {
|
|
224
|
+
const rule = rules.find((r) => r.id === "SCVS-2.12");
|
|
225
|
+
const bom = baseBom({
|
|
226
|
+
components: [
|
|
227
|
+
{
|
|
228
|
+
type: "library",
|
|
229
|
+
name: "bad",
|
|
230
|
+
version: "1.0.0",
|
|
231
|
+
purl: "not-a-purl",
|
|
232
|
+
"bom-ref": "bad",
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
const res = rule.evaluate(bom);
|
|
237
|
+
assert.strictEqual(res.status, "fail");
|
|
238
|
+
assert.ok(res.locations.length > 0);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("SCVS-2.15 rejects NOASSERTION-style license ids", () => {
|
|
242
|
+
const rule = rules.find((r) => r.id === "SCVS-2.15");
|
|
243
|
+
const bom = baseBom({
|
|
244
|
+
components: [
|
|
245
|
+
{
|
|
246
|
+
type: "library",
|
|
247
|
+
name: "x",
|
|
248
|
+
version: "1.0.0",
|
|
249
|
+
purl: "pkg:npm/x@1.0.0",
|
|
250
|
+
"bom-ref": "x",
|
|
251
|
+
licenses: [{ license: { id: "NOASSERTION" } }],
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
});
|
|
255
|
+
assert.strictEqual(rule.evaluate(bom).status, "fail");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("SCVS-3.20 flags orphan components not in dep graph", () => {
|
|
259
|
+
const rule = rules.find((r) => r.id === "SCVS-3.20");
|
|
260
|
+
const orphan = baseBom({
|
|
261
|
+
components: [
|
|
262
|
+
...baseBom().components,
|
|
263
|
+
{
|
|
264
|
+
type: "library",
|
|
265
|
+
name: "orphan",
|
|
266
|
+
version: "0.0.1",
|
|
267
|
+
purl: "pkg:npm/orphan@0.0.1",
|
|
268
|
+
"bom-ref": "pkg:npm/orphan@0.0.1",
|
|
269
|
+
licenses: [{ license: { id: "MIT" } }],
|
|
270
|
+
hashes: [{ alg: "SHA-256", content: "1" }],
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
});
|
|
274
|
+
const res = rule.evaluate(orphan);
|
|
275
|
+
assert.strictEqual(res.status, "fail");
|
|
276
|
+
assert.match(res.message, /not referenced/);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("SCVS-6.3 passes when no modified components exist", () => {
|
|
280
|
+
const rule = rules.find((r) => r.id === "SCVS-6.3");
|
|
281
|
+
assert.strictEqual(rule.evaluate(baseBom()).status, "pass");
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("CRA rules", () => {
|
|
286
|
+
const rules = getCraRules();
|
|
287
|
+
|
|
288
|
+
it("all pass on the well-formed baseline BOM", () => {
|
|
289
|
+
for (const r of rules) {
|
|
290
|
+
const res = r.evaluate(baseBom());
|
|
291
|
+
assert.strictEqual(
|
|
292
|
+
res.status,
|
|
293
|
+
"pass",
|
|
294
|
+
`${r.id} expected pass, got ${res.status}: ${res.message}`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("CRA-MIN-001 fails when supplier is missing", () => {
|
|
300
|
+
const rule = rules.find((r) => r.id === "CRA-MIN-001");
|
|
301
|
+
const bom = baseBom();
|
|
302
|
+
bom.metadata.supplier = undefined;
|
|
303
|
+
assert.strictEqual(rule.evaluate(bom).status, "fail");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("CRA-MIN-002 fails when contact is empty", () => {
|
|
307
|
+
const rule = rules.find((r) => r.id === "CRA-MIN-002");
|
|
308
|
+
const bom = baseBom();
|
|
309
|
+
bom.metadata.supplier = { name: "Acme" };
|
|
310
|
+
assert.strictEqual(rule.evaluate(bom).status, "fail");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("CRA-MIN-004 fails when dependency graph is empty", () => {
|
|
314
|
+
const rule = rules.find((r) => r.id === "CRA-MIN-004");
|
|
315
|
+
const bom = baseBom({ dependencies: [] });
|
|
316
|
+
assert.strictEqual(rule.evaluate(bom).status, "fail");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("CRA-MIN-008 supports both array (1.4) and object (1.5+) tool shapes", () => {
|
|
320
|
+
const rule = rules.find((r) => r.id === "CRA-MIN-008");
|
|
321
|
+
const legacy = baseBom();
|
|
322
|
+
legacy.metadata.tools = [{ name: "old" }];
|
|
323
|
+
assert.strictEqual(rule.evaluate(legacy).status, "pass");
|
|
324
|
+
const missing = baseBom();
|
|
325
|
+
missing.metadata.tools = undefined;
|
|
326
|
+
assert.strictEqual(rule.evaluate(missing).status, "fail");
|
|
327
|
+
});
|
|
328
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdx-validate orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Combines cdxgen's existing structural validation
|
|
5
|
+
* ({@link ./bomValidator.js}) with the compliance rule packs in
|
|
6
|
+
* {@link ./complianceEngine.js} and (optionally) signature verification from
|
|
7
|
+
* {@link ../helpers/bomSigner.js}.
|
|
8
|
+
*
|
|
9
|
+
* This module exposes a single high-level function, `validateBomAdvanced`,
|
|
10
|
+
* and helpers to classify the result. It does *not* perform any I/O: the CLI
|
|
11
|
+
* wrapper (`bin/validate.js`) is responsible for reading the input BOM.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { verifyNode } from "../helpers/bomSigner.js";
|
|
15
|
+
import {
|
|
16
|
+
validateBom,
|
|
17
|
+
validateMetadata,
|
|
18
|
+
validateProps,
|
|
19
|
+
validatePurls,
|
|
20
|
+
validateRefs,
|
|
21
|
+
} from "./bomValidator.js";
|
|
22
|
+
import { buildBenchmarkReports, evaluateAll } from "./complianceEngine.js";
|
|
23
|
+
|
|
24
|
+
const SEVERITY_ORDER = { info: 0, low: 1, medium: 2, high: 3, critical: 4 };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute the summary block of the report.
|
|
28
|
+
*
|
|
29
|
+
* @param {Array<object>} findings
|
|
30
|
+
* @returns {object}
|
|
31
|
+
*/
|
|
32
|
+
function summarize(findings) {
|
|
33
|
+
let pass = 0;
|
|
34
|
+
let failed = 0;
|
|
35
|
+
let manual = 0;
|
|
36
|
+
let errors = 0;
|
|
37
|
+
let warnings = 0;
|
|
38
|
+
for (const f of findings) {
|
|
39
|
+
if (f.status === "pass") pass += 1;
|
|
40
|
+
else if (f.status === "fail") failed += 1;
|
|
41
|
+
else if (f.status === "manual") manual += 1;
|
|
42
|
+
if (
|
|
43
|
+
f.status === "fail" &&
|
|
44
|
+
(f.severity === "high" || f.severity === "critical")
|
|
45
|
+
) {
|
|
46
|
+
errors += 1;
|
|
47
|
+
} else if (f.status === "fail") {
|
|
48
|
+
warnings += 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
total: findings.length,
|
|
53
|
+
pass,
|
|
54
|
+
fail: failed,
|
|
55
|
+
manual,
|
|
56
|
+
errors,
|
|
57
|
+
warnings,
|
|
58
|
+
schemaValid: true,
|
|
59
|
+
deepValid: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Filter findings by minimum severity and optional status inclusion rules.
|
|
65
|
+
*
|
|
66
|
+
* @param {Array<object>} findings
|
|
67
|
+
* @param {object} opts
|
|
68
|
+
* @param {string} [opts.minSeverity] "info"..."critical".
|
|
69
|
+
* @param {boolean} [opts.includeManual] When false, drop manual findings from
|
|
70
|
+
* the final array (they are still in
|
|
71
|
+
* the benchmark scorecards).
|
|
72
|
+
* @param {boolean} [opts.includePass] When false, drop pass findings.
|
|
73
|
+
* @returns {Array<object>}
|
|
74
|
+
*/
|
|
75
|
+
function filterFindings(findings, opts) {
|
|
76
|
+
const min = SEVERITY_ORDER[(opts.minSeverity || "info").toLowerCase()] ?? 0;
|
|
77
|
+
return findings.filter((f) => {
|
|
78
|
+
if (!opts.includeManual && f.status === "manual") return false;
|
|
79
|
+
if (!opts.includePass && f.status === "pass") return false;
|
|
80
|
+
return (SEVERITY_ORDER[f.severity] ?? 0) >= min;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Run schema + deep validation checks from the existing validator helpers and
|
|
86
|
+
* capture their boolean results without letting them blow up the process.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} bomJson
|
|
89
|
+
* @param {object} opts
|
|
90
|
+
* @returns {{ schemaValid: boolean, deepValid: boolean }}
|
|
91
|
+
*/
|
|
92
|
+
function runSchemaAndDeep(bomJson, opts) {
|
|
93
|
+
let schemaValid = true;
|
|
94
|
+
let deepValid = true;
|
|
95
|
+
if (opts.schema !== false) {
|
|
96
|
+
try {
|
|
97
|
+
schemaValid = validateBom(bomJson) !== false;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
schemaValid = false;
|
|
100
|
+
if (opts.onError) opts.onError("schema", err);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (opts.deep !== false) {
|
|
104
|
+
try {
|
|
105
|
+
deepValid =
|
|
106
|
+
validateMetadata(bomJson) !== false &&
|
|
107
|
+
validatePurls(bomJson) !== false &&
|
|
108
|
+
validateRefs(bomJson) !== false &&
|
|
109
|
+
validateProps(bomJson) !== false;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
deepValid = false;
|
|
112
|
+
if (opts.onError) opts.onError("deep", err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { schemaValid, deepValid };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Run structural + compliance validation against a parsed BOM.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} bomJson Parsed CycloneDX JSON BOM.
|
|
122
|
+
* @param {object} [options]
|
|
123
|
+
* @param {boolean} [options.schema] Run JSON-Schema validation (default true).
|
|
124
|
+
* @param {boolean} [options.deep] Run purl/ref/metadata deep checks (default true).
|
|
125
|
+
* @param {Array<string>} [options.benchmarks] Aliases to include in the scorecards (default: all).
|
|
126
|
+
* @param {Array<string>} [options.categories] Restrict compliance rules to these categories.
|
|
127
|
+
* @param {string} [options.minSeverity] Minimum severity for returned findings.
|
|
128
|
+
* @param {boolean} [options.includeManual] Include manual-review findings (default true).
|
|
129
|
+
* @param {boolean} [options.includePass] Include passing findings (default false).
|
|
130
|
+
* @param {string} [options.publicKey] If set, verify the BOM signature.
|
|
131
|
+
* @returns {{
|
|
132
|
+
* schemaValid: boolean,
|
|
133
|
+
* deepValid: boolean,
|
|
134
|
+
* signatureVerified: boolean | null,
|
|
135
|
+
* signatureDetails: object | null,
|
|
136
|
+
* findings: Array<object>,
|
|
137
|
+
* allFindings: Array<object>,
|
|
138
|
+
* benchmarks: Array<object>,
|
|
139
|
+
* summary: object
|
|
140
|
+
* }}
|
|
141
|
+
*/
|
|
142
|
+
export function validateBomAdvanced(bomJson, options = {}) {
|
|
143
|
+
const { schemaValid, deepValid } = runSchemaAndDeep(bomJson, options);
|
|
144
|
+
const allFindings = evaluateAll(bomJson, {
|
|
145
|
+
categories: options.categories,
|
|
146
|
+
benchmarks: options.benchmarks,
|
|
147
|
+
});
|
|
148
|
+
const benchmarks = buildBenchmarkReports(allFindings, options.benchmarks);
|
|
149
|
+
const filtered = filterFindings(allFindings, {
|
|
150
|
+
minSeverity: options.minSeverity || "info",
|
|
151
|
+
includeManual: options.includeManual !== false,
|
|
152
|
+
includePass: options.includePass === true,
|
|
153
|
+
});
|
|
154
|
+
let signatureVerified = null;
|
|
155
|
+
let signatureDetails = null;
|
|
156
|
+
if (options.publicKey && bomJson?.signature) {
|
|
157
|
+
try {
|
|
158
|
+
const match = verifyNode(bomJson, options.publicKey);
|
|
159
|
+
signatureVerified = Boolean(match);
|
|
160
|
+
signatureDetails = match || null;
|
|
161
|
+
} catch (err) {
|
|
162
|
+
signatureVerified = false;
|
|
163
|
+
signatureDetails = { error: err?.message || String(err) };
|
|
164
|
+
}
|
|
165
|
+
} else if (options.publicKey) {
|
|
166
|
+
// Public key provided but BOM has no signature.
|
|
167
|
+
signatureVerified = false;
|
|
168
|
+
signatureDetails = { error: "BOM has no signature block." };
|
|
169
|
+
}
|
|
170
|
+
const summary = summarize(allFindings);
|
|
171
|
+
summary.schemaValid = schemaValid;
|
|
172
|
+
summary.deepValid = deepValid;
|
|
173
|
+
return {
|
|
174
|
+
schemaValid,
|
|
175
|
+
deepValid,
|
|
176
|
+
signatureVerified,
|
|
177
|
+
signatureDetails,
|
|
178
|
+
findings: filtered,
|
|
179
|
+
allFindings,
|
|
180
|
+
benchmarks,
|
|
181
|
+
summary,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Decide whether a report should trigger a non-zero CLI exit.
|
|
187
|
+
*
|
|
188
|
+
* @param {object} report
|
|
189
|
+
* @param {object} opts
|
|
190
|
+
* @param {string} [opts.failSeverity] Severity level at or above which failing findings are considered a failure (default "high").
|
|
191
|
+
* @param {boolean} [opts.strict] When true, failing on any `fail` status regardless of severity, and a failing schema/deep validation also counts.
|
|
192
|
+
* @param {boolean} [opts.requireSignature] Require a valid signature when verification was requested.
|
|
193
|
+
* @returns {{ shouldFail: boolean, reason: string | null }}
|
|
194
|
+
*/
|
|
195
|
+
export function shouldFail(report, opts = {}) {
|
|
196
|
+
if (opts.requireSignature && report.signatureVerified === false) {
|
|
197
|
+
return { shouldFail: true, reason: "Signature verification failed." };
|
|
198
|
+
}
|
|
199
|
+
if (opts.strict && report.schemaValid === false) {
|
|
200
|
+
return { shouldFail: true, reason: "Schema validation failed." };
|
|
201
|
+
}
|
|
202
|
+
if (opts.strict && report.deepValid === false) {
|
|
203
|
+
return { shouldFail: true, reason: "Deep validation failed." };
|
|
204
|
+
}
|
|
205
|
+
const threshold =
|
|
206
|
+
SEVERITY_ORDER[(opts.failSeverity || "high").toLowerCase()] ??
|
|
207
|
+
SEVERITY_ORDER.high;
|
|
208
|
+
for (const f of report.allFindings || []) {
|
|
209
|
+
if (f.status !== "fail") continue;
|
|
210
|
+
const sev = SEVERITY_ORDER[f.severity] ?? 0;
|
|
211
|
+
if (sev >= threshold) {
|
|
212
|
+
return {
|
|
213
|
+
shouldFail: true,
|
|
214
|
+
reason: `Rule ${f.ruleId} failed with severity ${f.severity}.`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { shouldFail: false, reason: null };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export { buildBenchmarkReports, evaluateAll } from "./complianceEngine.js";
|
|
222
|
+
export { SEVERITY_ORDER };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import { shouldFail, validateBomAdvanced } from "./index.js";
|
|
4
|
+
|
|
5
|
+
function richBom() {
|
|
6
|
+
return {
|
|
7
|
+
bomFormat: "CycloneDX",
|
|
8
|
+
specVersion: "1.6",
|
|
9
|
+
serialNumber: "urn:uuid:1b671687-395b-41f5-a30f-a58921a69b79",
|
|
10
|
+
version: 1,
|
|
11
|
+
metadata: {
|
|
12
|
+
timestamp: "2024-02-02T00:00:00Z",
|
|
13
|
+
tools: {
|
|
14
|
+
components: [
|
|
15
|
+
{ type: "application", name: "cdxgen", version: "12.0.0" },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
component: {
|
|
19
|
+
name: "demo",
|
|
20
|
+
version: "1.0.0",
|
|
21
|
+
type: "application",
|
|
22
|
+
"bom-ref": "pkg:generic/demo@1.0.0",
|
|
23
|
+
},
|
|
24
|
+
supplier: {
|
|
25
|
+
name: "Acme",
|
|
26
|
+
contact: [{ email: "psirt@example.com" }],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
components: [
|
|
30
|
+
{
|
|
31
|
+
type: "library",
|
|
32
|
+
name: "lodash",
|
|
33
|
+
version: "4.17.21",
|
|
34
|
+
purl: "pkg:npm/lodash@4.17.21",
|
|
35
|
+
"bom-ref": "pkg:npm/lodash@4.17.21",
|
|
36
|
+
licenses: [{ license: { id: "MIT" } }],
|
|
37
|
+
hashes: [{ alg: "SHA-256", content: "x" }],
|
|
38
|
+
copyright: "© OpenJS",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
dependencies: [
|
|
42
|
+
{ ref: "pkg:generic/demo@1.0.0", dependsOn: ["pkg:npm/lodash@4.17.21"] },
|
|
43
|
+
{ ref: "pkg:npm/lodash@4.17.21", dependsOn: [] },
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("validateBomAdvanced", () => {
|
|
49
|
+
it("returns structural, compliance, and benchmark data", () => {
|
|
50
|
+
const report = validateBomAdvanced(richBom(), { schema: false });
|
|
51
|
+
assert.strictEqual(typeof report.schemaValid, "boolean");
|
|
52
|
+
assert.strictEqual(typeof report.deepValid, "boolean");
|
|
53
|
+
assert.ok(Array.isArray(report.findings));
|
|
54
|
+
assert.ok(Array.isArray(report.allFindings));
|
|
55
|
+
assert.ok(Array.isArray(report.benchmarks));
|
|
56
|
+
assert.ok(report.summary);
|
|
57
|
+
assert.strictEqual(report.summary.schemaValid, report.schemaValid);
|
|
58
|
+
assert.strictEqual(report.summary.deepValid, report.deepValid);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("hides pass findings by default but includes manual", () => {
|
|
62
|
+
const report = validateBomAdvanced(richBom(), { schema: false });
|
|
63
|
+
for (const f of report.findings) {
|
|
64
|
+
assert.notStrictEqual(f.status, "pass");
|
|
65
|
+
}
|
|
66
|
+
assert.ok(report.findings.some((f) => f.status === "manual"));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("respects minSeverity filter", () => {
|
|
70
|
+
const report = validateBomAdvanced(richBom(), {
|
|
71
|
+
schema: false,
|
|
72
|
+
minSeverity: "high",
|
|
73
|
+
includeManual: true,
|
|
74
|
+
});
|
|
75
|
+
for (const f of report.findings) {
|
|
76
|
+
assert.ok(["high", "critical"].includes(f.severity));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("marks signatureVerified=false when public key given but BOM unsigned", () => {
|
|
81
|
+
const report = validateBomAdvanced(richBom(), {
|
|
82
|
+
schema: false,
|
|
83
|
+
publicKey: "-----BEGIN PUBLIC KEY-----\nfake\n-----END PUBLIC KEY-----",
|
|
84
|
+
});
|
|
85
|
+
assert.strictEqual(report.signatureVerified, false);
|
|
86
|
+
assert.ok(report.signatureDetails?.error);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("returns signatureVerified=null when no public key supplied", () => {
|
|
90
|
+
const report = validateBomAdvanced(richBom(), { schema: false });
|
|
91
|
+
assert.strictEqual(report.signatureVerified, null);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("shouldFail", () => {
|
|
96
|
+
const fakeReport = (opts) => ({
|
|
97
|
+
schemaValid: opts.schemaValid ?? true,
|
|
98
|
+
deepValid: opts.deepValid ?? true,
|
|
99
|
+
signatureVerified: opts.signatureVerified ?? null,
|
|
100
|
+
allFindings: opts.findings || [],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("fails on any finding at or above fail-severity", () => {
|
|
104
|
+
const r = fakeReport({
|
|
105
|
+
findings: [{ status: "fail", severity: "high", ruleId: "X" }],
|
|
106
|
+
});
|
|
107
|
+
const { shouldFail: f } = shouldFail(r, { failSeverity: "high" });
|
|
108
|
+
assert.strictEqual(f, true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("does not fail when severity is below threshold", () => {
|
|
112
|
+
const r = fakeReport({
|
|
113
|
+
findings: [{ status: "fail", severity: "low", ruleId: "X" }],
|
|
114
|
+
});
|
|
115
|
+
const { shouldFail: f } = shouldFail(r, { failSeverity: "high" });
|
|
116
|
+
assert.strictEqual(f, false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("fails in strict mode on schema invalid", () => {
|
|
120
|
+
const r = fakeReport({ schemaValid: false });
|
|
121
|
+
assert.strictEqual(
|
|
122
|
+
shouldFail(r, { strict: true, failSeverity: "critical" }).shouldFail,
|
|
123
|
+
true,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("fails when signature required but verification failed", () => {
|
|
128
|
+
const r = fakeReport({ signatureVerified: false });
|
|
129
|
+
const { shouldFail: f, reason } = shouldFail(r, {
|
|
130
|
+
requireSignature: true,
|
|
131
|
+
failSeverity: "critical",
|
|
132
|
+
});
|
|
133
|
+
assert.strictEqual(f, true);
|
|
134
|
+
assert.match(reason, /Signature/);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("ignores signature when not required", () => {
|
|
138
|
+
const r = fakeReport({ signatureVerified: false });
|
|
139
|
+
assert.strictEqual(
|
|
140
|
+
shouldFail(r, { failSeverity: "critical" }).shouldFail,
|
|
141
|
+
false,
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
});
|