@cyclonedx/cdxgen 12.3.2 → 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.
Files changed (182) hide show
  1. package/README.md +70 -22
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +238 -116
  4. package/bin/convert.js +28 -13
  5. package/bin/hbom.js +490 -0
  6. package/bin/repl.js +580 -29
  7. package/bin/validate.js +34 -4
  8. package/bin/verify.js +40 -5
  9. package/data/README.md +298 -25
  10. package/data/component-tags.json +6 -0
  11. package/data/crypto-oid.json +16 -0
  12. package/data/predictive-audit-allowlist.json +11 -0
  13. package/data/queries-darwin.json +12 -1
  14. package/data/queries-win.json +7 -1
  15. package/data/queries.json +39 -2
  16. package/data/rules/ai-agent-governance.yaml +16 -0
  17. package/data/rules/asar-archives.yaml +150 -0
  18. package/data/rules/chrome-extensions.yaml +8 -0
  19. package/data/rules/ci-permissions.yaml +171 -15
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +76 -5
  22. package/data/rules/hbom-compliance.yaml +325 -0
  23. package/data/rules/hbom-performance.yaml +307 -0
  24. package/data/rules/hbom-security.yaml +248 -0
  25. package/data/rules/host-topology.yaml +165 -0
  26. package/data/rules/mcp-servers.yaml +18 -3
  27. package/data/rules/obom-runtime.yaml +907 -22
  28. package/data/rules/package-integrity.yaml +36 -0
  29. package/data/rules/rootfs-hardening.yaml +179 -0
  30. package/data/rules/vscode-extensions.yaml +9 -0
  31. package/lib/audit/index.js +209 -8
  32. package/lib/audit/index.poku.js +332 -0
  33. package/lib/audit/reporters.js +222 -0
  34. package/lib/audit/targets.js +146 -1
  35. package/lib/audit/targets.poku.js +186 -0
  36. package/lib/cli/asar.poku.js +328 -0
  37. package/lib/cli/index.js +647 -127
  38. package/lib/cli/index.poku.js +1905 -187
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/agentFormulationParser.js +6 -2
  41. package/lib/helpers/agentFormulationParser.poku.js +42 -0
  42. package/lib/helpers/analyzer.js +1444 -38
  43. package/lib/helpers/analyzer.poku.js +409 -0
  44. package/lib/helpers/analyzerScope.js +712 -0
  45. package/lib/helpers/asarutils.js +1556 -0
  46. package/lib/helpers/asarutils.poku.js +443 -0
  47. package/lib/helpers/auditCategories.js +12 -0
  48. package/lib/helpers/auditCategories.poku.js +32 -0
  49. package/lib/helpers/cbomutils.js +271 -1
  50. package/lib/helpers/cbomutils.poku.js +248 -5
  51. package/lib/helpers/chromextutils.js +25 -3
  52. package/lib/helpers/chromextutils.poku.js +68 -0
  53. package/lib/helpers/ciParsers/githubActions.js +79 -0
  54. package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
  55. package/lib/helpers/communityAiConfigParser.js +15 -5
  56. package/lib/helpers/communityAiConfigParser.poku.js +71 -0
  57. package/lib/helpers/depsUtils.js +5 -0
  58. package/lib/helpers/depsUtils.poku.js +55 -0
  59. package/lib/helpers/display.js +336 -23
  60. package/lib/helpers/display.poku.js +179 -43
  61. package/lib/helpers/evidenceUtils.js +58 -0
  62. package/lib/helpers/evidenceUtils.poku.js +54 -0
  63. package/lib/helpers/exportUtils.js +9 -0
  64. package/lib/helpers/gtfobins.js +142 -8
  65. package/lib/helpers/gtfobins.poku.js +24 -1
  66. package/lib/helpers/hbom.js +710 -0
  67. package/lib/helpers/hbom.poku.js +496 -0
  68. package/lib/helpers/hbomAnalysis.js +268 -0
  69. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  70. package/lib/helpers/hbomLoader.js +35 -0
  71. package/lib/helpers/hostTopology.js +803 -0
  72. package/lib/helpers/hostTopology.poku.js +363 -0
  73. package/lib/helpers/inventoryStats.js +69 -0
  74. package/lib/helpers/inventoryStats.poku.js +86 -0
  75. package/lib/helpers/lolbas.js +19 -1
  76. package/lib/helpers/lolbas.poku.js +23 -0
  77. package/lib/helpers/mcpConfigParser.js +21 -5
  78. package/lib/helpers/mcpConfigParser.poku.js +39 -2
  79. package/lib/helpers/osqueryTransform.js +47 -0
  80. package/lib/helpers/osqueryTransform.poku.js +47 -0
  81. package/lib/helpers/plugins.js +349 -0
  82. package/lib/helpers/plugins.poku.js +57 -0
  83. package/lib/helpers/propertySanitizer.js +121 -0
  84. package/lib/helpers/protobom.js +156 -45
  85. package/lib/helpers/protobom.poku.js +140 -5
  86. package/lib/helpers/remote/dependency-track.js +36 -3
  87. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  88. package/lib/helpers/source.js +24 -0
  89. package/lib/helpers/source.poku.js +32 -0
  90. package/lib/helpers/utils.js +2454 -198
  91. package/lib/helpers/utils.poku.js +1798 -74
  92. package/lib/managers/binary.e2e.poku.js +367 -0
  93. package/lib/managers/binary.js +2306 -350
  94. package/lib/managers/binary.poku.js +1700 -1
  95. package/lib/managers/docker.js +441 -95
  96. package/lib/managers/docker.poku.js +1479 -14
  97. package/lib/server/server.js +2 -24
  98. package/lib/server/server.poku.js +36 -1
  99. package/lib/stages/postgen/annotator.js +38 -0
  100. package/lib/stages/postgen/annotator.poku.js +107 -1
  101. package/lib/stages/postgen/auditBom.js +121 -18
  102. package/lib/stages/postgen/auditBom.poku.js +2967 -990
  103. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  104. package/lib/stages/postgen/postgen.js +192 -1
  105. package/lib/stages/postgen/postgen.poku.js +321 -0
  106. package/lib/stages/postgen/ruleEngine.js +116 -0
  107. package/lib/stages/pregen/envAudit.js +14 -3
  108. package/package.json +24 -21
  109. package/types/bin/hbom.d.ts +3 -0
  110. package/types/bin/hbom.d.ts.map +1 -0
  111. package/types/bin/repl.d.ts.map +1 -1
  112. package/types/lib/audit/index.d.ts +44 -0
  113. package/types/lib/audit/index.d.ts.map +1 -1
  114. package/types/lib/audit/reporters.d.ts +16 -0
  115. package/types/lib/audit/reporters.d.ts.map +1 -1
  116. package/types/lib/audit/targets.d.ts.map +1 -1
  117. package/types/lib/cli/index.d.ts +16 -0
  118. package/types/lib/cli/index.d.ts.map +1 -1
  119. package/types/lib/evinser/evinser.d.ts +4 -0
  120. package/types/lib/evinser/evinser.d.ts.map +1 -1
  121. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -1
  122. package/types/lib/helpers/analyzer.d.ts +33 -0
  123. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  124. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  125. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  126. package/types/lib/helpers/asarutils.d.ts +34 -0
  127. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  128. package/types/lib/helpers/auditCategories.d.ts +5 -0
  129. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  130. package/types/lib/helpers/cbomutils.d.ts +3 -2
  131. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  132. package/types/lib/helpers/chromextutils.d.ts.map +1 -1
  133. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  134. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -1
  135. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  136. package/types/lib/helpers/display.d.ts +1 -0
  137. package/types/lib/helpers/display.d.ts.map +1 -1
  138. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  139. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  140. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  141. package/types/lib/helpers/gtfobins.d.ts +8 -0
  142. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  143. package/types/lib/helpers/hbom.d.ts +49 -0
  144. package/types/lib/helpers/hbom.d.ts.map +1 -0
  145. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  146. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  147. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  148. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  149. package/types/lib/helpers/hostTopology.d.ts +12 -0
  150. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  151. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  152. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  153. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  154. package/types/lib/helpers/mcpConfigParser.d.ts +1 -1
  155. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -1
  156. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  157. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  158. package/types/lib/helpers/plugins.d.ts +58 -0
  159. package/types/lib/helpers/plugins.d.ts.map +1 -0
  160. package/types/lib/helpers/propertySanitizer.d.ts +3 -0
  161. package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
  162. package/types/lib/helpers/protobom.d.ts +3 -4
  163. package/types/lib/helpers/protobom.d.ts.map +1 -1
  164. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  165. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  166. package/types/lib/helpers/source.d.ts.map +1 -1
  167. package/types/lib/helpers/utils.d.ts +74 -8
  168. package/types/lib/helpers/utils.d.ts.map +1 -1
  169. package/types/lib/managers/binary.d.ts +5 -0
  170. package/types/lib/managers/binary.d.ts.map +1 -1
  171. package/types/lib/managers/docker.d.ts +3 -0
  172. package/types/lib/managers/docker.d.ts.map +1 -1
  173. package/types/lib/server/server.d.ts +2 -0
  174. package/types/lib/server/server.d.ts.map +1 -1
  175. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  176. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  177. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  178. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  179. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  180. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  181. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  182. package/data/spdx-model-v3.0.1.jsonld +0 -15999
@@ -0,0 +1,443 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as nodeFs from "node:fs";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ mkdtempSync,
7
+ readFileSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+
13
+ import esmock from "esmock";
14
+ import { assert, describe, it } from "poku";
15
+ import sinon from "sinon";
16
+
17
+ import {
18
+ createAsarFixture,
19
+ writeElectronAsarIntegrityPlist,
20
+ } from "../../test/helpers/asar-fixture-builder.js";
21
+ import {
22
+ cleanupAsarTempDir,
23
+ extractAsarToTempDir,
24
+ listAsarEntries,
25
+ parseAsarArchive,
26
+ readAsarArchiveHeaderSync,
27
+ rewriteExtractedArchivePaths,
28
+ } from "./asarutils.js";
29
+ import { safeRmSync } from "./utils.js";
30
+
31
+ const baseTempDir = mkdtempSync(join(tmpdir(), "cdxgen-asar-poku-"));
32
+
33
+ function align4(value) {
34
+ return value + ((4 - (value % 4)) % 4);
35
+ }
36
+
37
+ function makeStringPickle(value) {
38
+ const valueBuffer = Buffer.from(value, "utf8");
39
+ const alignedStringLength = align4(valueBuffer.length);
40
+ const payloadLength = 4 + alignedStringLength;
41
+ const buffer = Buffer.alloc(4 + payloadLength);
42
+ buffer.writeUInt32LE(payloadLength, 0);
43
+ buffer.writeInt32LE(valueBuffer.length, 4);
44
+ valueBuffer.copy(buffer, 8);
45
+ return buffer;
46
+ }
47
+
48
+ function makeSizePickle(value) {
49
+ const buffer = Buffer.alloc(8);
50
+ buffer.writeUInt32LE(4, 0);
51
+ buffer.writeUInt32LE(value, 4);
52
+ return buffer;
53
+ }
54
+
55
+ function rewriteArchiveHeaderSync(archivePath, transformHeader) {
56
+ const archiveBuffer = readFileSync(archivePath);
57
+ const headerPickleSize = archiveBuffer.readUInt32LE(4);
58
+ const headerBuffer = archiveBuffer.subarray(8, 8 + headerPickleSize);
59
+ const headerStringLength = headerBuffer.readInt32LE(4);
60
+ const headerString = headerBuffer.toString("utf8", 8, 8 + headerStringLength);
61
+ const nextHeader = transformHeader(JSON.parse(headerString));
62
+ const nextHeaderPickle = makeStringPickle(JSON.stringify(nextHeader));
63
+ writeFileSync(
64
+ archivePath,
65
+ Buffer.concat([
66
+ makeSizePickle(nextHeaderPickle.length),
67
+ nextHeaderPickle,
68
+ archiveBuffer.subarray(8 + headerPickleSize),
69
+ ]),
70
+ );
71
+ }
72
+
73
+ process.on("exit", () => {
74
+ safeRmSync(baseTempDir, { force: true, recursive: true });
75
+ });
76
+
77
+ describe("extractAsarToTempDir()", () => {
78
+ it("returns undefined when dry-run blocks ASAR extraction", async () => {
79
+ const safeExtractArchive = sinon.stub().resolves(false);
80
+ const { extractAsarToTempDir: extractAsarToTempDirMocked } = await esmock(
81
+ "./asarutils.js",
82
+ {
83
+ "./utils.js": {
84
+ DEBUG_MODE: false,
85
+ getTmpDir: sinon.stub().returns("/tmp"),
86
+ isDryRun: false,
87
+ recordActivity: sinon.stub(),
88
+ safeCopyFileSync: sinon.stub(),
89
+ safeExtractArchive,
90
+ safeMkdirSync: sinon.stub(),
91
+ safeMkdtempSync: sinon.stub().returns("/tmp/asar-deps-test"),
92
+ safeRmSync: sinon.stub(),
93
+ safeWriteSync: sinon.stub(),
94
+ },
95
+ },
96
+ );
97
+
98
+ const extractedDir = await extractAsarToTempDirMocked("/tmp/sample.asar");
99
+
100
+ assert.strictEqual(extractedDir, undefined);
101
+ sinon.assert.calledOnce(safeExtractArchive);
102
+ });
103
+ });
104
+
105
+ describe("parseAsarArchive()", () => {
106
+ it("catalogs file inventory, hashes, evidence, and security-sensitive properties", async () => {
107
+ const archivePath = join(baseTempDir, "fixture.asar");
108
+ createAsarFixture(archivePath, {
109
+ corruptIntegrityPaths: ["config/settings.json"],
110
+ executablePaths: ["scripts/postinstall.js"],
111
+ symlinks: {
112
+ "config-link": "config/settings.json",
113
+ },
114
+ unpackedPaths: ["native/addon.node"],
115
+ });
116
+
117
+ const analysis = await parseAsarArchive(archivePath, {});
118
+ const entryList = listAsarEntries(archivePath);
119
+
120
+ assert.ok(entryList.entries.some((entry) => entry.path === "config-link"));
121
+ assert.strictEqual(analysis.parentComponent.name, "Sample Electron App");
122
+ assert.strictEqual(
123
+ analysis.parentComponent.purl,
124
+ "pkg:npm/sample-electron-app@1.2.3",
125
+ );
126
+ assert.strictEqual(
127
+ analysis.summary.integrityMismatchCount,
128
+ 1,
129
+ "expected one mismatched declared integrity hash",
130
+ );
131
+ assert.ok(analysis.summary.capabilities.includes("fileAccess"));
132
+ assert.ok(analysis.summary.capabilities.includes("network"));
133
+ assert.ok(analysis.summary.capabilities.includes("hardware"));
134
+ assert.ok(analysis.summary.capabilities.includes("dynamicFetch"));
135
+ assert.ok(analysis.summary.capabilities.includes("dynamicImport"));
136
+ assert.strictEqual(analysis.summary.hasEval, true);
137
+ const archiveProps = analysis.parentComponent.properties;
138
+ assert.strictEqual(
139
+ archiveProps.find((property) => property.name === "cdx:asar:hasEval")
140
+ ?.value,
141
+ "true",
142
+ );
143
+ assert.strictEqual(
144
+ archiveProps.find(
145
+ (property) => property.name === "cdx:asar:hasNativeAddons",
146
+ )?.value,
147
+ "true",
148
+ );
149
+ assert.strictEqual(
150
+ archiveProps.find(
151
+ (property) => property.name === "cdx:asar:hasIntegrityMismatch",
152
+ )?.value,
153
+ "true",
154
+ );
155
+
156
+ const mainFileComponent = analysis.components.find((component) =>
157
+ component.properties?.some(
158
+ (property) =>
159
+ property.name === "cdx:asar:path" && property.value === "src/main.js",
160
+ ),
161
+ );
162
+ assert.ok(mainFileComponent, "expected src/main.js file component");
163
+ assert.ok(mainFileComponent.hashes?.length, "expected SHA-256 hash");
164
+ assert.strictEqual(
165
+ mainFileComponent.evidence?.occurrences?.[0]?.location,
166
+ `${archivePath}#/src/main.js`,
167
+ );
168
+ assert.strictEqual(
169
+ mainFileComponent.properties.find(
170
+ (property) => property.name === "cdx:asar:js:hasDynamicFetch",
171
+ )?.value,
172
+ "true",
173
+ );
174
+ assert.strictEqual(
175
+ mainFileComponent.properties.find(
176
+ (property) => property.name === "cdx:asar:js:capability:hardware",
177
+ )?.value,
178
+ "true",
179
+ );
180
+
181
+ const unpackedComponent = analysis.components.find((component) =>
182
+ component.properties?.some(
183
+ (property) =>
184
+ property.name === "cdx:asar:path" &&
185
+ property.value === "native/addon.node",
186
+ ),
187
+ );
188
+ assert.ok(unpackedComponent, "expected native addon component");
189
+ assert.strictEqual(
190
+ unpackedComponent.properties.find(
191
+ (property) => property.name === "cdx:asar:unpacked",
192
+ )?.value,
193
+ "true",
194
+ );
195
+ });
196
+
197
+ it("extracts ASAR archives and rewrites extracted source paths back to archive paths", async () => {
198
+ const archivePath = join(baseTempDir, "fixture-extract.asar");
199
+ createAsarFixture(archivePath, {
200
+ unpackedPaths: ["native/addon.node"],
201
+ });
202
+
203
+ const extractedDir = await extractAsarToTempDir(archivePath);
204
+ assert.ok(extractedDir, "expected extraction temp dir");
205
+ assert.ok(existsSync(join(extractedDir, "src", "main.js")));
206
+ assert.ok(existsSync(join(extractedDir, "native", "addon.node")));
207
+
208
+ const component = {
209
+ evidence: {
210
+ identity: {
211
+ methods: [
212
+ {
213
+ confidence: 1,
214
+ technique: "manifest-analysis",
215
+ value: join(extractedDir, "package.json"),
216
+ },
217
+ ],
218
+ },
219
+ occurrences: [
220
+ {
221
+ location: join(extractedDir, "src", "main.js"),
222
+ },
223
+ ],
224
+ },
225
+ properties: [
226
+ {
227
+ name: "SrcFile",
228
+ value: join(
229
+ extractedDir,
230
+ "node_modules",
231
+ "sketchy-addon",
232
+ "package.json",
233
+ ),
234
+ },
235
+ ],
236
+ };
237
+ rewriteExtractedArchivePaths(component, extractedDir, archivePath);
238
+ assert.strictEqual(
239
+ component.properties[0].value,
240
+ `${archivePath}#/node_modules/sketchy-addon/package.json`,
241
+ );
242
+ assert.strictEqual(
243
+ component.evidence.identity.methods[0].value,
244
+ `${archivePath}#/package.json`,
245
+ );
246
+ assert.strictEqual(
247
+ component.evidence.occurrences[0].location,
248
+ `${archivePath}#/src/main.js`,
249
+ );
250
+
251
+ cleanupAsarTempDir(extractedDir);
252
+ assert.ok(!existsSync(extractedDir), "expected extracted temp dir cleanup");
253
+ });
254
+
255
+ it("verifies Electron ASAR signing metadata and emits a crypto component", async () => {
256
+ const appDir = join(baseTempDir, "Signed.app");
257
+ const archivePath = join(
258
+ appDir,
259
+ "Contents",
260
+ "Resources",
261
+ "app & signed.asar",
262
+ );
263
+ mkdirSync(join(appDir, "Contents", "Resources"), { recursive: true });
264
+ createAsarFixture(archivePath);
265
+ const { headerString } = readAsarArchiveHeaderSync(archivePath);
266
+ const headerHash = createHash("sha256")
267
+ .update(headerString, "utf8")
268
+ .digest("hex");
269
+ writeElectronAsarIntegrityPlist(join(appDir, "Contents", "Info.plist"), {
270
+ "Resources/app & signed.asar": {
271
+ algorithm: "SHA256",
272
+ hash: headerHash,
273
+ },
274
+ });
275
+
276
+ const analysis = await parseAsarArchive(archivePath, { specVersion: 1.7 });
277
+ const signingComponent = analysis.components.find(
278
+ (component) =>
279
+ component.type === "cryptographic-asset" &&
280
+ component.properties?.some(
281
+ (property) =>
282
+ property.name === "cdx:asar:signingVerified" &&
283
+ property.value === "true",
284
+ ),
285
+ );
286
+ assert.strictEqual(
287
+ analysis.parentComponent.properties.find(
288
+ (property) => property.name === "cdx:asar:hasSigningMetadata",
289
+ )?.value,
290
+ "true",
291
+ );
292
+ assert.strictEqual(
293
+ analysis.parentComponent.properties.find(
294
+ (property) => property.name === "cdx:asar:signingVerified",
295
+ )?.value,
296
+ "true",
297
+ );
298
+ assert.strictEqual(
299
+ analysis.parentComponent.properties.find(
300
+ (property) => property.name === "cdx:asar:signingScope",
301
+ )?.value,
302
+ "header-only",
303
+ );
304
+ assert.ok(signingComponent, "expected ASAR signing crypto component");
305
+ assert.strictEqual(
306
+ signingComponent.properties.find(
307
+ (property) => property.name === "cdx:asar:signingScope",
308
+ )?.value,
309
+ "header-only",
310
+ );
311
+ assert.ok(
312
+ analysis.dependencies.some(
313
+ (dependency) =>
314
+ dependency.ref === analysis.parentComponent["bom-ref"] &&
315
+ dependency.dependsOn.includes(signingComponent["bom-ref"]),
316
+ ),
317
+ "expected parent archive to depend on the signing component",
318
+ );
319
+ });
320
+
321
+ it("rejects ASAR headers with oversized file entries", async () => {
322
+ const archivePath = join(baseTempDir, "fixture-oversized.asar");
323
+ createAsarFixture(archivePath, {
324
+ extraEntries: {
325
+ "huge.bin": {
326
+ content: "x",
327
+ size: 256 * 1024 * 1024 + 1,
328
+ },
329
+ },
330
+ });
331
+
332
+ await assert.rejects(
333
+ () => parseAsarArchive(archivePath, {}),
334
+ /Invalid ASAR file entry/,
335
+ );
336
+ });
337
+
338
+ it("rejects ASAR entries with offsets beyond the safe read limit", async () => {
339
+ const archivePath = join(baseTempDir, "fixture-offset.asar");
340
+ createAsarFixture(archivePath, {
341
+ extraEntries: {
342
+ "too-far.bin": {
343
+ content: "x",
344
+ offset: Number.MAX_SAFE_INTEGER + 10,
345
+ size: 1,
346
+ },
347
+ },
348
+ });
349
+
350
+ await assert.rejects(
351
+ () => parseAsarArchive(archivePath, {}),
352
+ /offset exceeds the safe read limit/,
353
+ );
354
+ });
355
+
356
+ it("rejects ASAR headers with excessive nesting depth", async () => {
357
+ const archivePath = join(baseTempDir, "fixture-deep.asar");
358
+ const deeplyNestedPath = `${Array.from({ length: 260 }, (_, index) => `d${index}`).join("/")}/payload.txt`;
359
+ createAsarFixture(archivePath, {
360
+ extraEntries: {
361
+ [deeplyNestedPath]: {
362
+ content: "payload",
363
+ },
364
+ },
365
+ });
366
+
367
+ await assert.rejects(
368
+ () => parseAsarArchive(archivePath, {}),
369
+ /nesting exceeds 256 levels/,
370
+ );
371
+ });
372
+
373
+ it("rejects ASAR headers with conflicting entry kinds", async () => {
374
+ const archivePath = join(baseTempDir, "fixture-conflicting-kinds.asar");
375
+ createAsarFixture(archivePath);
376
+ rewriteArchiveHeaderSync(archivePath, (header) => {
377
+ header.files["bad-link"] = {
378
+ files: {},
379
+ link: "src/main.js",
380
+ };
381
+ return header;
382
+ });
383
+
384
+ await assert.rejects(
385
+ () => parseAsarArchive(archivePath, {}),
386
+ /Invalid ASAR symlink entry/,
387
+ );
388
+ });
389
+
390
+ it("rejects symlinks that escape the extraction root", async () => {
391
+ const archivePath = join(baseTempDir, "fixture-link-escape.asar");
392
+ createAsarFixture(archivePath, {
393
+ symlinks: {
394
+ "escape-link": "../../outside.txt",
395
+ },
396
+ });
397
+
398
+ const extractedDir = await extractAsarToTempDir(archivePath);
399
+ assert.strictEqual(extractedDir, undefined);
400
+ });
401
+
402
+ it("rejects circular symlink chains during extraction", async () => {
403
+ const archivePath = join(baseTempDir, "fixture-link-cycle.asar");
404
+ createAsarFixture(archivePath, {
405
+ symlinks: {
406
+ a: "b",
407
+ b: "a",
408
+ },
409
+ });
410
+
411
+ const extractedDir = await extractAsarToTempDir(archivePath);
412
+ assert.strictEqual(extractedDir, undefined);
413
+ });
414
+
415
+ it("reuses one packed-entry file descriptor per parse and extraction pass", async () => {
416
+ const archivePath = join(baseTempDir, "fixture-open-reuse.asar");
417
+ createAsarFixture(archivePath, {
418
+ unpackedPaths: ["native/addon.node"],
419
+ });
420
+ const openSync = sinon.spy((...args) => nodeFs.openSync(...args));
421
+ const closeSync = sinon.spy((...args) => nodeFs.closeSync(...args));
422
+ const {
423
+ cleanupAsarTempDir: cleanupAsarTempDirMocked,
424
+ extractAsarToTempDir: extractAsarToTempDirMocked,
425
+ parseAsarArchive: parseAsarArchiveMocked,
426
+ } = await esmock("./asarutils.js", {
427
+ "node:fs": {
428
+ ...nodeFs,
429
+ closeSync,
430
+ openSync,
431
+ },
432
+ });
433
+
434
+ await parseAsarArchiveMocked(archivePath, {});
435
+ const extractedDir = await extractAsarToTempDirMocked(archivePath);
436
+
437
+ assert.ok(extractedDir, "expected extraction temp dir");
438
+ assert.strictEqual(openSync.callCount, 4);
439
+ assert.strictEqual(closeSync.callCount, 4);
440
+
441
+ cleanupAsarTempDirMocked(extractedDir);
442
+ });
443
+ });
@@ -1,5 +1,17 @@
1
+ export const HBOM_AUDIT_CATEGORIES = Object.freeze([
2
+ "hbom-security",
3
+ "hbom-performance",
4
+ "hbom-compliance",
5
+ ]);
6
+
7
+ export const HOST_TOPOLOGY_AUDIT_CATEGORIES = Object.freeze(["host-topology"]);
8
+
9
+ export const DEFAULT_HBOM_AUDIT_CATEGORIES = HBOM_AUDIT_CATEGORIES.join(",");
10
+
1
11
  export const BOM_AUDIT_CATEGORY_ALIASES = Object.freeze({
2
12
  "ai-inventory": ["ai-agent", "mcp-server"],
13
+ hbom: [...HBOM_AUDIT_CATEGORIES],
14
+ host: [...HBOM_AUDIT_CATEGORIES, ...HOST_TOPOLOGY_AUDIT_CATEGORIES],
3
15
  });
4
16
 
5
17
  function uniqueNonEmptyCategories(categories) {
@@ -0,0 +1,32 @@
1
+ import { assert, describe, it } from "poku";
2
+
3
+ import {
4
+ expandBomAuditCategories,
5
+ validateBomAuditCategories,
6
+ } from "./auditCategories.js";
7
+
8
+ describe("auditCategories", () => {
9
+ it("keeps host-topology as a direct category", () => {
10
+ assert.deepStrictEqual(expandBomAuditCategories("host-topology"), [
11
+ "host-topology",
12
+ ]);
13
+ });
14
+
15
+ it("expands the host alias to the HBOM packs plus host-topology", () => {
16
+ assert.deepStrictEqual(expandBomAuditCategories("host"), [
17
+ "hbom-security",
18
+ "hbom-performance",
19
+ "hbom-compliance",
20
+ "host-topology",
21
+ ]);
22
+ });
23
+
24
+ it("accepts host-topology during validation", () => {
25
+ const validation = validateBomAuditCategories("host-topology", [
26
+ { category: "host-topology" },
27
+ { category: "hbom-security" },
28
+ ]);
29
+ assert.deepStrictEqual(validation.categories, ["host-topology"]);
30
+ assert.deepStrictEqual(validation.expandedCategories, ["host-topology"]);
31
+ });
32
+ });