@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,186 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+
4
+ import { getGtfoBinsMetadata } from "./gtfobins.js";
5
+ import { dirNameStr, safeExistsSync } from "./utils.js";
6
+
7
+ const CONTAINER_RISK_INDEX_FILE = join(
8
+ dirNameStr,
9
+ "data",
10
+ "container-knowledge-index.json",
11
+ );
12
+ const DEFAULT_CONTAINER_RISK_INDEX = { entries: {}, sources: {} };
13
+ const CONTAINER_RISK_INDEX = loadContainerRiskIndex();
14
+
15
+ function loadContainerRiskIndex() {
16
+ if (!safeExistsSync(CONTAINER_RISK_INDEX_FILE)) {
17
+ return DEFAULT_CONTAINER_RISK_INDEX;
18
+ }
19
+ try {
20
+ return JSON.parse(readFileSync(CONTAINER_RISK_INDEX_FILE, "utf8"));
21
+ } catch {
22
+ return DEFAULT_CONTAINER_RISK_INDEX;
23
+ }
24
+ }
25
+
26
+ function normalizeCandidate(candidate) {
27
+ if (!candidate || typeof candidate !== "string") {
28
+ return undefined;
29
+ }
30
+ const trimmed = basename(candidate.trim()).toLowerCase();
31
+ if (!trimmed) {
32
+ return undefined;
33
+ }
34
+ return trimmed;
35
+ }
36
+
37
+ function uniqueSortedStrings(values) {
38
+ return Array.from(
39
+ new Set(
40
+ values.filter(
41
+ (value) => typeof value === "string" && value.trim().length,
42
+ ),
43
+ ),
44
+ ).sort();
45
+ }
46
+
47
+ function resolveContainerEntry(name, linkedName, gtfoMetadata) {
48
+ const directCandidate = normalizeCandidate(name);
49
+ if (directCandidate && CONTAINER_RISK_INDEX.entries?.[directCandidate]) {
50
+ return {
51
+ canonicalName: directCandidate,
52
+ entry: CONTAINER_RISK_INDEX.entries[directCandidate],
53
+ matchSource: "basename",
54
+ };
55
+ }
56
+ const linkedCandidate = normalizeCandidate(linkedName);
57
+ if (linkedCandidate && CONTAINER_RISK_INDEX.entries?.[linkedCandidate]) {
58
+ return {
59
+ canonicalName: linkedCandidate,
60
+ entry: CONTAINER_RISK_INDEX.entries[linkedCandidate],
61
+ matchSource: "symlink",
62
+ };
63
+ }
64
+ const gtfoCandidate = normalizeCandidate(gtfoMetadata?.canonicalName);
65
+ if (gtfoCandidate && CONTAINER_RISK_INDEX.entries?.[gtfoCandidate]) {
66
+ return {
67
+ canonicalName: gtfoCandidate,
68
+ entry: CONTAINER_RISK_INDEX.entries[gtfoCandidate],
69
+ matchSource: "gtfobins",
70
+ };
71
+ }
72
+ return undefined;
73
+ }
74
+
75
+ function resolveKnowledgeSourceRefs(sourceKeys) {
76
+ const refs = [];
77
+ for (const sourceKey of sourceKeys || []) {
78
+ const ref = CONTAINER_RISK_INDEX.sources?.[sourceKey];
79
+ if (ref) {
80
+ refs.push(ref);
81
+ }
82
+ }
83
+ return uniqueSortedStrings(refs);
84
+ }
85
+
86
+ export function getContainerRiskMetadata(name, linkedName) {
87
+ const gtfoMetadata = getGtfoBinsMetadata(name, linkedName);
88
+ const resolvedEntry = resolveContainerEntry(name, linkedName, gtfoMetadata);
89
+ if (!resolvedEntry) {
90
+ return undefined;
91
+ }
92
+ const attackTactics = uniqueSortedStrings(
93
+ resolvedEntry.entry.attackTactics || [],
94
+ );
95
+ const attackTechniques = uniqueSortedStrings([
96
+ ...(resolvedEntry.entry.attackTechniques || []),
97
+ ...(gtfoMetadata?.mitreTechniques || []),
98
+ ]);
99
+ const knowledgeSources = uniqueSortedStrings(
100
+ resolvedEntry.entry.sourceKeys || [],
101
+ );
102
+ const knowledgeSourceRefs = resolveKnowledgeSourceRefs(knowledgeSources);
103
+ const offenseTools = uniqueSortedStrings(
104
+ resolvedEntry.entry.offenseTools || [],
105
+ );
106
+ const riskTags = uniqueSortedStrings([
107
+ ...(resolvedEntry.entry.riskTags || []),
108
+ ...(gtfoMetadata?.riskTags || []),
109
+ ]);
110
+ const seccompBlockedSyscalls = uniqueSortedStrings(
111
+ resolvedEntry.entry.seccompBlockedSyscalls || [],
112
+ );
113
+ return {
114
+ attackTactics,
115
+ attackTechniques,
116
+ canonicalName: resolvedEntry.canonicalName,
117
+ knowledgeSourceRefs,
118
+ knowledgeSources,
119
+ matchSource: resolvedEntry.matchSource,
120
+ offenseTools,
121
+ riskTags,
122
+ seccompBlockedSyscalls,
123
+ seccompProfile: resolvedEntry.entry.seccompProfile || "",
124
+ };
125
+ }
126
+
127
+ export function createContainerRiskProperties(name, linkedName) {
128
+ const metadata = getContainerRiskMetadata(name, linkedName);
129
+ if (!metadata) {
130
+ return [];
131
+ }
132
+ const properties = [
133
+ { name: "cdx:container:matched", value: "true" },
134
+ { name: "cdx:container:name", value: metadata.canonicalName },
135
+ { name: "cdx:container:matchSource", value: metadata.matchSource },
136
+ ];
137
+ if (metadata.attackTactics.length) {
138
+ properties.push({
139
+ name: "cdx:container:attackTactics",
140
+ value: metadata.attackTactics.join(","),
141
+ });
142
+ }
143
+ if (metadata.attackTechniques.length) {
144
+ properties.push({
145
+ name: "cdx:container:attackTechniques",
146
+ value: metadata.attackTechniques.join(","),
147
+ });
148
+ }
149
+ if (metadata.knowledgeSources.length) {
150
+ properties.push({
151
+ name: "cdx:container:knowledgeSources",
152
+ value: metadata.knowledgeSources.join(","),
153
+ });
154
+ }
155
+ if (metadata.knowledgeSourceRefs.length) {
156
+ properties.push({
157
+ name: "cdx:container:knowledgeSourceRefs",
158
+ value: metadata.knowledgeSourceRefs.join(","),
159
+ });
160
+ }
161
+ if (metadata.offenseTools.length) {
162
+ properties.push({
163
+ name: "cdx:container:offenseTools",
164
+ value: metadata.offenseTools.join(","),
165
+ });
166
+ }
167
+ if (metadata.riskTags.length) {
168
+ properties.push({
169
+ name: "cdx:container:riskTags",
170
+ value: metadata.riskTags.join(","),
171
+ });
172
+ }
173
+ if (metadata.seccompBlockedSyscalls.length) {
174
+ properties.push({
175
+ name: "cdx:container:seccompBlockedSyscalls",
176
+ value: metadata.seccompBlockedSyscalls.join(","),
177
+ });
178
+ }
179
+ if (metadata.seccompProfile) {
180
+ properties.push({
181
+ name: "cdx:container:seccompProfile",
182
+ value: metadata.seccompProfile,
183
+ });
184
+ }
185
+ return properties;
186
+ }
@@ -0,0 +1,52 @@
1
+ import { strict as assert } from "node:assert";
2
+
3
+ import { describe, it } from "poku";
4
+
5
+ import {
6
+ createContainerRiskProperties,
7
+ getContainerRiskMetadata,
8
+ } from "./containerRisk.js";
9
+
10
+ describe("container risk helpers", () => {
11
+ it("returns offensive toolkit metadata for direct container tool matches", () => {
12
+ const metadata = getContainerRiskMetadata("cdk");
13
+ assert.ok(metadata);
14
+ assert.strictEqual(metadata.canonicalName, "cdk");
15
+ assert.ok(metadata.offenseTools.includes("cdk"));
16
+ assert.ok(metadata.riskTags.includes("offensive-toolkit"));
17
+ assert.ok(metadata.attackTechniques.includes("T1611"));
18
+ });
19
+
20
+ it("maps kubernetes control-plane helpers to ATT&CK and offensive playbooks", () => {
21
+ const metadata = getContainerRiskMetadata("kubectl");
22
+ assert.ok(metadata);
23
+ assert.ok(metadata.offenseTools.includes("peirates"));
24
+ assert.ok(metadata.offenseTools.includes("cdk"));
25
+ assert.ok(metadata.attackTechniques.includes("T1613"));
26
+ assert.ok(metadata.riskTags.includes("k8s-cluster-pivot"));
27
+ });
28
+
29
+ it("tracks seccomp-sensitive namespace escape helpers", () => {
30
+ const metadata = getContainerRiskMetadata("nsenter");
31
+ assert.ok(metadata);
32
+ assert.strictEqual(metadata.seccompProfile, "docker-default");
33
+ assert.ok(metadata.seccompBlockedSyscalls.includes("setns"));
34
+ assert.ok(metadata.seccompBlockedSyscalls.includes("unshare"));
35
+ });
36
+
37
+ it("emits stable CycloneDX properties for enriched container binaries", () => {
38
+ const properties = createContainerRiskProperties("docker");
39
+ const propertyMap = Object.fromEntries(
40
+ properties.map((property) => [property.name, property.value]),
41
+ );
42
+ assert.strictEqual(propertyMap["cdx:container:matched"], "true");
43
+ assert.strictEqual(propertyMap["cdx:container:name"], "docker");
44
+ assert.ok(propertyMap["cdx:container:attackTechniques"].includes("T1611"));
45
+ assert.ok(propertyMap["cdx:container:offenseTools"].includes("deepce"));
46
+ assert.ok(
47
+ propertyMap["cdx:container:knowledgeSources"].includes(
48
+ "attack-containers",
49
+ ),
50
+ );
51
+ });
52
+ });
@@ -119,6 +119,22 @@ export function trimComponents(components) {
119
119
  existingComponent.properties = comp.properties;
120
120
  }
121
121
  }
122
+ if (comp.hashes) {
123
+ if (existingComponent.hashes) {
124
+ for (const newhash of comp.hashes) {
125
+ if (
126
+ !existingComponent.hashes.find(
127
+ (hash) =>
128
+ hash.alg === newhash.alg && hash.content === newhash.content,
129
+ )
130
+ ) {
131
+ existingComponent.hashes.push(newhash);
132
+ }
133
+ }
134
+ } else {
135
+ existingComponent.hashes = comp.hashes;
136
+ }
137
+ }
122
138
  // Retain all component.evidence.identity
123
139
  if (comp?.evidence?.identity) {
124
140
  if (!existingComponent.evidence) {
@@ -1,6 +1,6 @@
1
1
  import { assert, describe, it } from "poku";
2
2
 
3
- import { mergeDependencies } from "./depsUtils.js";
3
+ import { mergeDependencies, trimComponents } from "./depsUtils.js";
4
4
 
5
5
  describe("mergeDependencies()", () => {
6
6
  it("merges two non-overlapping dependency arrays", () => {
@@ -148,3 +148,60 @@ describe("mergeDependencies()", () => {
148
148
  assert.ok(!entry.dependsOn.includes(null), "null must be filtered");
149
149
  });
150
150
  });
151
+
152
+ describe("trimComponents()", () => {
153
+ it("retains hashes from duplicate components", () => {
154
+ const components = [
155
+ {
156
+ name: "jquery",
157
+ version: "3.5.1",
158
+ purl: "pkg:npm/jquery@3.5.1",
159
+ type: "library",
160
+ properties: [{ name: "SrcFile", value: "Scripts/jquery.min.js" }],
161
+ },
162
+ {
163
+ name: "jquery",
164
+ version: "3.5.1",
165
+ purl: "pkg:npm/jquery@3.5.1",
166
+ type: "framework",
167
+ hashes: [{ alg: "SHA-512", content: "abc123" }],
168
+ properties: [{ name: "SrcFile", value: "package-lock.json" }],
169
+ },
170
+ ];
171
+ const result = trimComponents(components);
172
+ assert.strictEqual(result.length, 1);
173
+ assert.deepStrictEqual(result[0].hashes, [
174
+ { alg: "SHA-512", content: "abc123" },
175
+ ]);
176
+ });
177
+
178
+ it("merges and deduplicates hashes from duplicate components", () => {
179
+ const components = [
180
+ {
181
+ name: "jquery",
182
+ version: "3.5.1",
183
+ purl: "pkg:npm/jquery@3.5.1",
184
+ type: "library",
185
+ hashes: [{ alg: "SHA-512", content: "abc123" }],
186
+ properties: [{ name: "SrcFile", value: "Scripts/jquery.min.js" }],
187
+ },
188
+ {
189
+ name: "jquery",
190
+ version: "3.5.1",
191
+ purl: "pkg:npm/jquery@3.5.1",
192
+ type: "framework",
193
+ hashes: [
194
+ { alg: "SHA-512", content: "abc123" },
195
+ { alg: "SHA-256", content: "def456" },
196
+ ],
197
+ properties: [{ name: "SrcFile", value: "package-lock.json" }],
198
+ },
199
+ ];
200
+ const result = trimComponents(components);
201
+ assert.strictEqual(result.length, 1);
202
+ assert.deepStrictEqual(result[0].hashes, [
203
+ { alg: "SHA-512", content: "abc123" },
204
+ { alg: "SHA-256", content: "def456" },
205
+ ]);
206
+ });
207
+ });