@cyclonedx/cdxgen 12.3.3 → 12.4.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 +64 -22
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +238 -116
- package/bin/convert.js +28 -13
- package/bin/hbom.js +490 -0
- package/bin/repl.js +580 -29
- package/bin/validate.js +34 -4
- package/bin/verify.js +40 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/queries-darwin.json +12 -1
- package/data/queries-win.json +7 -1
- package/data/queries.json +39 -2
- package/data/rules/ai-agent-governance.yaml +16 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/chrome-extensions.yaml +8 -0
- package/data/rules/ci-permissions.yaml +42 -18
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +11 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +18 -3
- package/data/rules/obom-runtime.yaml +907 -22
- package/data/rules/package-integrity.yaml +14 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +9 -0
- package/lib/audit/index.js +209 -8
- package/lib/audit/index.poku.js +332 -0
- package/lib/audit/reporters.js +222 -0
- package/lib/audit/targets.js +146 -1
- package/lib/audit/targets.poku.js +186 -0
- package/lib/cli/asar.poku.js +328 -0
- package/lib/cli/index.js +506 -88
- package/lib/cli/index.poku.js +1352 -212
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/analyzer.js +1406 -29
- package/lib/helpers/analyzer.poku.js +342 -0
- package/lib/helpers/analyzerScope.js +712 -0
- package/lib/helpers/asarutils.js +1556 -0
- package/lib/helpers/asarutils.poku.js +443 -0
- package/lib/helpers/auditCategories.js +12 -0
- package/lib/helpers/auditCategories.poku.js +32 -0
- package/lib/helpers/cbomutils.js +271 -1
- package/lib/helpers/cbomutils.poku.js +248 -5
- package/lib/helpers/display.js +291 -1
- package/lib/helpers/display.poku.js +149 -0
- package/lib/helpers/evidenceUtils.js +58 -0
- package/lib/helpers/evidenceUtils.poku.js +54 -0
- package/lib/helpers/exportUtils.js +9 -0
- package/lib/helpers/gtfobins.js +142 -8
- package/lib/helpers/gtfobins.poku.js +24 -1
- package/lib/helpers/hbom.js +710 -0
- package/lib/helpers/hbom.poku.js +496 -0
- package/lib/helpers/hbomAnalysis.js +268 -0
- package/lib/helpers/hbomAnalysis.poku.js +249 -0
- package/lib/helpers/hbomLoader.js +35 -0
- package/lib/helpers/hostTopology.js +803 -0
- package/lib/helpers/hostTopology.poku.js +363 -0
- package/lib/helpers/inventoryStats.js +69 -0
- package/lib/helpers/inventoryStats.poku.js +86 -0
- package/lib/helpers/lolbas.js +19 -1
- package/lib/helpers/lolbas.poku.js +23 -0
- package/lib/helpers/osqueryTransform.js +47 -0
- package/lib/helpers/osqueryTransform.poku.js +47 -0
- package/lib/helpers/plugins.js +349 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/protobom.js +156 -45
- package/lib/helpers/protobom.poku.js +140 -5
- package/lib/helpers/remote/dependency-track.js +36 -3
- package/lib/helpers/remote/dependency-track.poku.js +44 -0
- package/lib/helpers/source.js +24 -0
- package/lib/helpers/source.poku.js +32 -0
- package/lib/helpers/utils.js +1438 -93
- package/lib/helpers/utils.poku.js +846 -4
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2293 -353
- package/lib/managers/binary.poku.js +1699 -1
- package/lib/managers/docker.js +201 -79
- package/lib/managers/docker.poku.js +337 -12
- package/lib/server/server.js +2 -27
- package/lib/stages/postgen/annotator.js +38 -0
- package/lib/stages/postgen/annotator.poku.js +107 -1
- package/lib/stages/postgen/auditBom.js +121 -18
- package/lib/stages/postgen/auditBom.poku.js +1366 -31
- package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
- package/lib/stages/postgen/postgen.js +192 -1
- package/lib/stages/postgen/postgen.poku.js +321 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/package.json +23 -21
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +44 -0
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts +16 -0
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +16 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +4 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +33 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/analyzerScope.d.ts +11 -0
- package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
- package/types/lib/helpers/asarutils.d.ts +34 -0
- package/types/lib/helpers/asarutils.d.ts.map +1 -0
- package/types/lib/helpers/auditCategories.d.ts +5 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +3 -2
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/evidenceUtils.d.ts +8 -0
- package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +8 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -1
- package/types/lib/helpers/hbom.d.ts +49 -0
- package/types/lib/helpers/hbom.d.ts.map +1 -0
- package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
- package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/helpers/hbomLoader.d.ts +7 -0
- package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
- package/types/lib/helpers/hostTopology.d.ts +12 -0
- package/types/lib/helpers/hostTopology.d.ts.map +1 -0
- package/types/lib/helpers/inventoryStats.d.ts +11 -0
- package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -1
- package/types/lib/helpers/osqueryTransform.d.ts +3 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
- package/types/lib/helpers/plugins.d.ts +58 -0
- package/types/lib/helpers/plugins.d.ts.map +1 -0
- package/types/lib/helpers/protobom.d.ts +3 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +45 -8
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +5 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +2 -1
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +26 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +2 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/data/spdx-model-v3.0.1.jsonld +0 -15999
package/lib/audit/targets.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
1
4
|
import { PackageURL } from "packageurl-js";
|
|
2
5
|
|
|
3
6
|
import { hasTrustedPublishingProperties } from "../helpers/provenanceUtils.js";
|
|
4
7
|
import {
|
|
8
|
+
dirNameStr,
|
|
5
9
|
getCratesMetadata,
|
|
6
10
|
getNpmMetadata,
|
|
7
11
|
getPyMetadata,
|
|
@@ -9,13 +13,126 @@ import {
|
|
|
9
13
|
|
|
10
14
|
const SUPPORTED_PURL_TYPES = new Set(["cargo", "npm", "pypi"]);
|
|
11
15
|
const NON_REQUIRED_SCOPES = new Set(["excluded", "optional"]);
|
|
16
|
+
const BUILTIN_PREDICTIVE_AUDIT_ALLOWLIST = Object.freeze(
|
|
17
|
+
loadAllowlistPrefixes(
|
|
18
|
+
join(dirNameStr, "data", "predictive-audit-allowlist.json"),
|
|
19
|
+
"built-in predictive audit allowlist",
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function normalizeAllowlistPrefix(prefix) {
|
|
24
|
+
if (typeof prefix !== "string") {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const normalizedPrefix = prefix.trim().toLowerCase();
|
|
28
|
+
return normalizedPrefix.startsWith("pkg:") ? normalizedPrefix : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeAllowlistPrefixes(prefixes) {
|
|
32
|
+
return [
|
|
33
|
+
...new Set((prefixes || []).map(normalizeAllowlistPrefix).filter(Boolean)),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseAllowlistPrefixes(rawContent, sourceLabel) {
|
|
38
|
+
const trimmedContent = rawContent?.trim();
|
|
39
|
+
if (!trimmedContent) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
if (trimmedContent.startsWith("[") || trimmedContent.startsWith("{")) {
|
|
43
|
+
const parsedValue = JSON.parse(trimmedContent);
|
|
44
|
+
if (Array.isArray(parsedValue)) {
|
|
45
|
+
return normalizeAllowlistPrefixes(parsedValue);
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(parsedValue?.prefixes)) {
|
|
48
|
+
return normalizeAllowlistPrefixes(parsedValue.prefixes);
|
|
49
|
+
}
|
|
50
|
+
throw new Error(
|
|
51
|
+
`${sourceLabel} must be a JSON array or an object with a 'prefixes' array.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return normalizeAllowlistPrefixes(
|
|
55
|
+
trimmedContent
|
|
56
|
+
.split(/\r?\n/u)
|
|
57
|
+
.map((line) => line.replace(/\s+#.*$/u, "").trim())
|
|
58
|
+
.filter(Boolean),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function loadAllowlistPrefixes(filePath, sourceLabel) {
|
|
63
|
+
let rawContent;
|
|
64
|
+
try {
|
|
65
|
+
rawContent = readFileSync(filePath, "utf8");
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const errorCodeSuffix =
|
|
68
|
+
typeof error?.code === "string" ? ` (${error.code})` : "";
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Failed to load ${sourceLabel} from ${filePath}${errorCodeSuffix}.`,
|
|
71
|
+
{
|
|
72
|
+
cause: error,
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
return parseAllowlistPrefixes(rawContent, sourceLabel);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Invalid ${sourceLabel} in ${filePath}. Use a JSON array, a JSON object with a 'prefixes' array, or newline-delimited purl prefixes.`,
|
|
81
|
+
{
|
|
82
|
+
cause: error,
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getAllowlistPrefixes(options) {
|
|
89
|
+
const customPrefixes = options?.allowlistFile
|
|
90
|
+
? loadAllowlistPrefixes(options.allowlistFile, "predictive audit allowlist")
|
|
91
|
+
: normalizeAllowlistPrefixes(options?.allowlistPrefixes);
|
|
92
|
+
return normalizeAllowlistPrefixes([
|
|
93
|
+
...BUILTIN_PREDICTIVE_AUDIT_ALLOWLIST,
|
|
94
|
+
...customPrefixes,
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Find the first allowlisted purl prefix that matches a component purl using
|
|
100
|
+
* a real purl boundary.
|
|
101
|
+
*
|
|
102
|
+
* This avoids over-filtering when one package name is only a lexical prefix of
|
|
103
|
+
* another package name (for example `pkg:npm/npm` vs `pkg:npm/npm-run-all`).
|
|
104
|
+
*
|
|
105
|
+
* @param {string | undefined} componentPurl Candidate component purl
|
|
106
|
+
* @param {string[] | undefined} allowlistPrefixes Normalized allowlist prefixes
|
|
107
|
+
* @returns {string | undefined} matched allowlist prefix, if any
|
|
108
|
+
*/
|
|
109
|
+
function findMatchingAllowlistPrefix(componentPurl, allowlistPrefixes) {
|
|
110
|
+
const normalizedPurl = componentPurl?.toLowerCase();
|
|
111
|
+
if (!normalizedPurl || !allowlistPrefixes?.length) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
return allowlistPrefixes.find((prefix) => {
|
|
115
|
+
if (!normalizedPurl.startsWith(prefix)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const boundaryCharacter = normalizedPurl[prefix.length];
|
|
119
|
+
return (
|
|
120
|
+
// Purl boundaries after a matched prefix can be the version separator,
|
|
121
|
+
// namespace/package separator, qualifier separator, or subpath separator.
|
|
122
|
+
boundaryCharacter === undefined ||
|
|
123
|
+
["/", "@", "?", "#"].includes(boundaryCharacter)
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
12
127
|
|
|
13
128
|
/**
|
|
14
129
|
* Normalize predictive audit target selection options.
|
|
15
130
|
*
|
|
16
131
|
* @param {number | object | undefined} options selector options or legacy maxTargets value
|
|
17
132
|
* @returns {{
|
|
133
|
+
* allowlistPrefixes: string[],
|
|
18
134
|
* maxTargets: number | undefined,
|
|
135
|
+
* prioritizeDirectRuntime: boolean,
|
|
19
136
|
* scope: string | undefined,
|
|
20
137
|
* trusted: "exclude" | "include" | "only",
|
|
21
138
|
* }} normalized options
|
|
@@ -23,6 +140,7 @@ const NON_REQUIRED_SCOPES = new Set(["excluded", "optional"]);
|
|
|
23
140
|
function normalizeTargetSelectionOptions(options) {
|
|
24
141
|
if (typeof options === "number") {
|
|
25
142
|
return {
|
|
143
|
+
allowlistPrefixes: BUILTIN_PREDICTIVE_AUDIT_ALLOWLIST,
|
|
26
144
|
maxTargets: options,
|
|
27
145
|
prioritizeDirectRuntime: true,
|
|
28
146
|
scope: undefined,
|
|
@@ -30,6 +148,7 @@ function normalizeTargetSelectionOptions(options) {
|
|
|
30
148
|
};
|
|
31
149
|
}
|
|
32
150
|
return {
|
|
151
|
+
allowlistPrefixes: getAllowlistPrefixes(options),
|
|
33
152
|
maxTargets: options?.maxTargets,
|
|
34
153
|
prioritizeDirectRuntime: options?.prioritizeDirectRuntime ?? true,
|
|
35
154
|
scope: options?.scope === "required" ? "required" : undefined,
|
|
@@ -452,6 +571,22 @@ export function extractPurlTargetsFromBom(bomJson, sourceName, options) {
|
|
|
452
571
|
});
|
|
453
572
|
continue;
|
|
454
573
|
}
|
|
574
|
+
const matchedAllowlistPrefix = findMatchingAllowlistPrefix(
|
|
575
|
+
componentPurl,
|
|
576
|
+
selectorOptions.allowlistPrefixes,
|
|
577
|
+
);
|
|
578
|
+
if (matchedAllowlistPrefix) {
|
|
579
|
+
skipped.push({
|
|
580
|
+
reason: "allowlisted-purl-prefix",
|
|
581
|
+
matchedPrefix: matchedAllowlistPrefix,
|
|
582
|
+
source: sourceName,
|
|
583
|
+
purl: componentPurl,
|
|
584
|
+
bomRef: component?.["bom-ref"],
|
|
585
|
+
name: component?.name,
|
|
586
|
+
type: purlObj.type,
|
|
587
|
+
});
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
455
590
|
targets.push({
|
|
456
591
|
bomRef: component?.["bom-ref"],
|
|
457
592
|
buildOnlyWorkspace:
|
|
@@ -511,6 +646,7 @@ export function extractPurlTargetsFromBom(bomJson, sourceName, options) {
|
|
|
511
646
|
*/
|
|
512
647
|
export function collectAuditTargets(inputBoms, options) {
|
|
513
648
|
const selectorOptions = normalizeTargetSelectionOptions(options);
|
|
649
|
+
const allowlistedTargetPurls = new Set();
|
|
514
650
|
const skipped = [];
|
|
515
651
|
const targetMap = new Map();
|
|
516
652
|
for (const inputBom of inputBoms) {
|
|
@@ -519,7 +655,15 @@ export function collectAuditTargets(inputBoms, options) {
|
|
|
519
655
|
inputBom.source,
|
|
520
656
|
selectorOptions,
|
|
521
657
|
);
|
|
522
|
-
|
|
658
|
+
for (const skippedEntry of extracted.skipped) {
|
|
659
|
+
skipped.push(skippedEntry);
|
|
660
|
+
if (
|
|
661
|
+
skippedEntry?.reason === "allowlisted-purl-prefix" &&
|
|
662
|
+
skippedEntry?.purl
|
|
663
|
+
) {
|
|
664
|
+
allowlistedTargetPurls.add(skippedEntry.purl);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
523
667
|
for (const target of extracted.targets) {
|
|
524
668
|
const existing = targetMap.get(target.purl);
|
|
525
669
|
if (existing) {
|
|
@@ -630,6 +774,7 @@ export function collectAuditTargets(inputBoms, options) {
|
|
|
630
774
|
skipped,
|
|
631
775
|
stats: {
|
|
632
776
|
availableTargets,
|
|
777
|
+
allowlistedTargetsExcluded: allowlistedTargetPurls.size,
|
|
633
778
|
directRuntimeTargets: directRuntimeTargets.length,
|
|
634
779
|
buildOnlyWorkspaceTargets: buildOnlyWorkspaceTargets.length,
|
|
635
780
|
cargoRuntimeFacingTargets: cargoRuntimeFacingTargets.length,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
1
5
|
import esmock from "esmock";
|
|
2
6
|
import { assert, describe, it } from "poku";
|
|
3
7
|
|
|
@@ -17,6 +21,42 @@ function makeBom(components, extra = {}) {
|
|
|
17
21
|
};
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
function makeAllowlistInputBom(source) {
|
|
25
|
+
return {
|
|
26
|
+
bomJson: makeBom([
|
|
27
|
+
{
|
|
28
|
+
"bom-ref": "pkg:npm/%40acme/core@1.0.0",
|
|
29
|
+
name: "core",
|
|
30
|
+
purl: "pkg:npm/%40acme/core@1.0.0",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"bom-ref": "pkg:pypi/internal-tool@1.0.0",
|
|
34
|
+
name: "internal-tool",
|
|
35
|
+
purl: "pkg:pypi/internal-tool@1.0.0",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"bom-ref": "pkg:npm/left-pad@1.3.0",
|
|
39
|
+
name: "left-pad",
|
|
40
|
+
purl: "pkg:npm/left-pad@1.3.0",
|
|
41
|
+
},
|
|
42
|
+
]),
|
|
43
|
+
source,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function withTemporaryAllowlistFile(fileName, content, callback) {
|
|
48
|
+
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "cdx-audit-allowlist-"));
|
|
49
|
+
const allowlistFile = path.join(tmpDir, fileName);
|
|
50
|
+
writeFileSync(allowlistFile, content);
|
|
51
|
+
try {
|
|
52
|
+
// Let test failures propagate after cleanup so the original assertion error
|
|
53
|
+
// is preserved for the runner.
|
|
54
|
+
callback(allowlistFile);
|
|
55
|
+
} finally {
|
|
56
|
+
rmSync(tmpDir, { force: true, recursive: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
20
60
|
describe("normalizePackageName()", () => {
|
|
21
61
|
it("normalizes Python-style package separators", () => {
|
|
22
62
|
assert.strictEqual(
|
|
@@ -312,6 +352,152 @@ describe("collectAuditTargets()", () => {
|
|
|
312
352
|
assert.strictEqual(collected.stats.nonRequiredTargets, 0);
|
|
313
353
|
});
|
|
314
354
|
|
|
355
|
+
it("skips built-in well-known npm allowlist prefixes by default", () => {
|
|
356
|
+
const inputBoms = [
|
|
357
|
+
{
|
|
358
|
+
bomJson: makeBom([
|
|
359
|
+
{
|
|
360
|
+
"bom-ref": "pkg:npm/%40babel/parser@7.29.3",
|
|
361
|
+
name: "parser",
|
|
362
|
+
purl: "pkg:npm/%40babel/parser@7.29.3",
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"bom-ref": "pkg:npm/npm@10.9.0",
|
|
366
|
+
name: "npm",
|
|
367
|
+
purl: "pkg:npm/npm@10.9.0",
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"bom-ref": "pkg:npm/%40types/node@24.0.0",
|
|
371
|
+
name: "node",
|
|
372
|
+
purl: "pkg:npm/%40types/node@24.0.0",
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"bom-ref": "pkg:npm/left-pad@1.3.0",
|
|
376
|
+
name: "left-pad",
|
|
377
|
+
purl: "pkg:npm/left-pad@1.3.0",
|
|
378
|
+
},
|
|
379
|
+
]),
|
|
380
|
+
source: "allowlist-default.json",
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
const collected = collectAuditTargets(inputBoms, { trusted: "include" });
|
|
385
|
+
|
|
386
|
+
assert.deepStrictEqual(
|
|
387
|
+
collected.targets.map((target) => target.purl),
|
|
388
|
+
["pkg:npm/left-pad@1.3.0"],
|
|
389
|
+
);
|
|
390
|
+
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 3);
|
|
391
|
+
assert.strictEqual(
|
|
392
|
+
collected.skipped.filter(
|
|
393
|
+
(entry) => entry.reason === "allowlisted-purl-prefix",
|
|
394
|
+
).length,
|
|
395
|
+
3,
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("supports additive custom predictive audit allowlists", () => {
|
|
400
|
+
withTemporaryAllowlistFile(
|
|
401
|
+
"custom-allowlist.json",
|
|
402
|
+
`${JSON.stringify(["pkg:npm/%40acme", "pkg:pypi/internal-tool"])}\n`,
|
|
403
|
+
(allowlistFile) => {
|
|
404
|
+
const inputBoms = [makeAllowlistInputBom("allowlist-custom.json")];
|
|
405
|
+
|
|
406
|
+
const collected = collectAuditTargets(inputBoms, {
|
|
407
|
+
allowlistFile,
|
|
408
|
+
trusted: "include",
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
assert.deepStrictEqual(
|
|
412
|
+
collected.targets.map((target) => target.purl),
|
|
413
|
+
["pkg:npm/left-pad@1.3.0"],
|
|
414
|
+
);
|
|
415
|
+
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 2);
|
|
416
|
+
},
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("supports newline-delimited custom allowlists with comments", () => {
|
|
421
|
+
withTemporaryAllowlistFile(
|
|
422
|
+
"custom-allowlist.txt",
|
|
423
|
+
[
|
|
424
|
+
"pkg:npm/%40acme # internal namespace",
|
|
425
|
+
"",
|
|
426
|
+
"pkg:pypi/internal-tool",
|
|
427
|
+
].join("\n"),
|
|
428
|
+
(allowlistFile) => {
|
|
429
|
+
const inputBoms = [makeAllowlistInputBom("allowlist-custom-text.json")];
|
|
430
|
+
|
|
431
|
+
const collected = collectAuditTargets(inputBoms, {
|
|
432
|
+
allowlistFile,
|
|
433
|
+
trusted: "include",
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
assert.deepStrictEqual(
|
|
437
|
+
collected.targets.map((target) => target.purl),
|
|
438
|
+
["pkg:npm/left-pad@1.3.0"],
|
|
439
|
+
);
|
|
440
|
+
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 2);
|
|
441
|
+
},
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("supports custom allowlists provided as a prefixes object", () => {
|
|
446
|
+
withTemporaryAllowlistFile(
|
|
447
|
+
"custom-allowlist.json",
|
|
448
|
+
`${JSON.stringify({ prefixes: ["pkg:npm/%40acme", "pkg:pypi/internal-tool"] })}\n`,
|
|
449
|
+
(allowlistFile) => {
|
|
450
|
+
const inputBoms = [
|
|
451
|
+
makeAllowlistInputBom("allowlist-custom-prefixes.json"),
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
const collected = collectAuditTargets(inputBoms, {
|
|
455
|
+
allowlistFile,
|
|
456
|
+
trusted: "include",
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
assert.deepStrictEqual(
|
|
460
|
+
collected.targets.map((target) => target.purl),
|
|
461
|
+
["pkg:npm/left-pad@1.3.0"],
|
|
462
|
+
);
|
|
463
|
+
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 2);
|
|
464
|
+
},
|
|
465
|
+
);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("requires a purl boundary after an allowlisted prefix", () => {
|
|
469
|
+
const inputBoms = [
|
|
470
|
+
{
|
|
471
|
+
bomJson: makeBom([
|
|
472
|
+
{
|
|
473
|
+
"bom-ref": "pkg:npm/npm@10.9.0",
|
|
474
|
+
name: "npm",
|
|
475
|
+
purl: "pkg:npm/npm@10.9.0",
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
"bom-ref": "pkg:npm/npm-run-all@4.1.5",
|
|
479
|
+
name: "npm-run-all",
|
|
480
|
+
purl: "pkg:npm/npm-run-all@4.1.5",
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
"bom-ref": "pkg:npm/npm-check-updates@17.1.0",
|
|
484
|
+
name: "npm-check-updates",
|
|
485
|
+
purl: "pkg:npm/npm-check-updates@17.1.0",
|
|
486
|
+
},
|
|
487
|
+
]),
|
|
488
|
+
source: "allowlist-boundary.json",
|
|
489
|
+
},
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
const collected = collectAuditTargets(inputBoms, { trusted: "include" });
|
|
493
|
+
|
|
494
|
+
assert.deepStrictEqual(
|
|
495
|
+
collected.targets.map((target) => target.purl),
|
|
496
|
+
["pkg:npm/npm-check-updates@17.1.0", "pkg:npm/npm-run-all@4.1.5"],
|
|
497
|
+
);
|
|
498
|
+
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 1);
|
|
499
|
+
});
|
|
500
|
+
|
|
315
501
|
it("prioritizes required targets before optional ones when maxTargets is set", () => {
|
|
316
502
|
const inputBoms = [
|
|
317
503
|
{
|