@cyclonedx/cdxgen 12.3.3 → 12.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +64 -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 +42 -18
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +11 -0
  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 +14 -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 +506 -88
  38. package/lib/cli/index.poku.js +1352 -212
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/analyzer.js +1406 -29
  41. package/lib/helpers/analyzer.poku.js +342 -0
  42. package/lib/helpers/analyzerScope.js +712 -0
  43. package/lib/helpers/asarutils.js +1556 -0
  44. package/lib/helpers/asarutils.poku.js +443 -0
  45. package/lib/helpers/auditCategories.js +12 -0
  46. package/lib/helpers/auditCategories.poku.js +32 -0
  47. package/lib/helpers/cbomutils.js +271 -1
  48. package/lib/helpers/cbomutils.poku.js +248 -5
  49. package/lib/helpers/display.js +291 -1
  50. package/lib/helpers/display.poku.js +149 -0
  51. package/lib/helpers/evidenceUtils.js +58 -0
  52. package/lib/helpers/evidenceUtils.poku.js +54 -0
  53. package/lib/helpers/exportUtils.js +9 -0
  54. package/lib/helpers/gtfobins.js +142 -8
  55. package/lib/helpers/gtfobins.poku.js +24 -1
  56. package/lib/helpers/hbom.js +710 -0
  57. package/lib/helpers/hbom.poku.js +496 -0
  58. package/lib/helpers/hbomAnalysis.js +268 -0
  59. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  60. package/lib/helpers/hbomLoader.js +35 -0
  61. package/lib/helpers/hostTopology.js +803 -0
  62. package/lib/helpers/hostTopology.poku.js +363 -0
  63. package/lib/helpers/inventoryStats.js +69 -0
  64. package/lib/helpers/inventoryStats.poku.js +86 -0
  65. package/lib/helpers/lolbas.js +19 -1
  66. package/lib/helpers/lolbas.poku.js +23 -0
  67. package/lib/helpers/osqueryTransform.js +47 -0
  68. package/lib/helpers/osqueryTransform.poku.js +47 -0
  69. package/lib/helpers/plugins.js +349 -0
  70. package/lib/helpers/plugins.poku.js +57 -0
  71. package/lib/helpers/protobom.js +156 -45
  72. package/lib/helpers/protobom.poku.js +140 -5
  73. package/lib/helpers/remote/dependency-track.js +36 -3
  74. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  75. package/lib/helpers/source.js +24 -0
  76. package/lib/helpers/source.poku.js +32 -0
  77. package/lib/helpers/utils.js +1438 -93
  78. package/lib/helpers/utils.poku.js +846 -4
  79. package/lib/managers/binary.e2e.poku.js +367 -0
  80. package/lib/managers/binary.js +2293 -353
  81. package/lib/managers/binary.poku.js +1699 -1
  82. package/lib/managers/docker.js +201 -79
  83. package/lib/managers/docker.poku.js +337 -12
  84. package/lib/server/server.js +2 -27
  85. package/lib/stages/postgen/annotator.js +38 -0
  86. package/lib/stages/postgen/annotator.poku.js +107 -1
  87. package/lib/stages/postgen/auditBom.js +121 -18
  88. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  89. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  90. package/lib/stages/postgen/postgen.js +192 -1
  91. package/lib/stages/postgen/postgen.poku.js +321 -0
  92. package/lib/stages/postgen/ruleEngine.js +116 -0
  93. package/lib/stages/pregen/envAudit.js +14 -3
  94. package/package.json +23 -21
  95. package/types/bin/hbom.d.ts +3 -0
  96. package/types/bin/hbom.d.ts.map +1 -0
  97. package/types/bin/repl.d.ts.map +1 -1
  98. package/types/lib/audit/index.d.ts +44 -0
  99. package/types/lib/audit/index.d.ts.map +1 -1
  100. package/types/lib/audit/reporters.d.ts +16 -0
  101. package/types/lib/audit/reporters.d.ts.map +1 -1
  102. package/types/lib/audit/targets.d.ts.map +1 -1
  103. package/types/lib/cli/index.d.ts +16 -0
  104. package/types/lib/cli/index.d.ts.map +1 -1
  105. package/types/lib/evinser/evinser.d.ts +4 -0
  106. package/types/lib/evinser/evinser.d.ts.map +1 -1
  107. package/types/lib/helpers/analyzer.d.ts +33 -0
  108. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  109. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  110. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  111. package/types/lib/helpers/asarutils.d.ts +34 -0
  112. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  113. package/types/lib/helpers/auditCategories.d.ts +5 -0
  114. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  115. package/types/lib/helpers/cbomutils.d.ts +3 -2
  116. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  117. package/types/lib/helpers/display.d.ts.map +1 -1
  118. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  119. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  120. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  121. package/types/lib/helpers/gtfobins.d.ts +8 -0
  122. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  123. package/types/lib/helpers/hbom.d.ts +49 -0
  124. package/types/lib/helpers/hbom.d.ts.map +1 -0
  125. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  126. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  127. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  128. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  129. package/types/lib/helpers/hostTopology.d.ts +12 -0
  130. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  131. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  132. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  133. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  134. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  135. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  136. package/types/lib/helpers/plugins.d.ts +58 -0
  137. package/types/lib/helpers/plugins.d.ts.map +1 -0
  138. package/types/lib/helpers/protobom.d.ts +3 -4
  139. package/types/lib/helpers/protobom.d.ts.map +1 -1
  140. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  141. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  142. package/types/lib/helpers/source.d.ts.map +1 -1
  143. package/types/lib/helpers/utils.d.ts +45 -8
  144. package/types/lib/helpers/utils.d.ts.map +1 -1
  145. package/types/lib/managers/binary.d.ts +5 -0
  146. package/types/lib/managers/binary.d.ts.map +1 -1
  147. package/types/lib/managers/docker.d.ts.map +1 -1
  148. package/types/lib/server/server.d.ts +2 -1
  149. package/types/lib/server/server.d.ts.map +1 -1
  150. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  151. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  152. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  153. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  154. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  155. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  156. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  157. package/data/spdx-model-v3.0.1.jsonld +0 -15999
@@ -12,8 +12,11 @@ import { URL } from "node:url";
12
12
  import { assert, describe, it } from "poku";
13
13
 
14
14
  import {
15
+ analyzeJsCapabilitiesFile,
16
+ analyzeJsCryptoFile,
15
17
  analyzeSuspiciousJsFile,
16
18
  detectExtensionCapabilities,
19
+ detectJsCryptoInventory,
17
20
  detectMcpInventory,
18
21
  detectPythonMcpInventory,
19
22
  findJSImportsExports,
@@ -255,6 +258,24 @@ wasi.start(instance);
255
258
  );
256
259
  }
257
260
  });
261
+
262
+ it("honors exclude globs during JS import/export discovery", async () => {
263
+ const projectDir = createProjectFiles("imports-with-excludes", {
264
+ "src/index.js":
265
+ "import { readFileSync } from 'node:fs';\nvoid readFileSync;\n",
266
+ "test/ignored.js": "import net from 'node:net';\nvoid net;\n",
267
+ "node_modules/demo/index.js": "import tls from 'node:tls';\nvoid tls;\n",
268
+ });
269
+
270
+ const { allImports } = await findJSImportsExports(projectDir, {
271
+ deep: true,
272
+ exclude: ["**/test/**", "**/node_modules/**"],
273
+ });
274
+
275
+ assert.ok(allImports["node:fs"]);
276
+ assert.equal(allImports["node:net"], undefined);
277
+ assert.equal(allImports["node:tls"], undefined);
278
+ });
258
279
  });
259
280
 
260
281
  describe("detectExtensionCapabilities()", () => {
@@ -324,7 +345,328 @@ describe("analyzeSuspiciousJsFile()", () => {
324
345
  });
325
346
  });
326
347
 
348
+ describe("analyzeJsCapabilitiesFile()", () => {
349
+ it("detects file, network, hardware, child-process, and dynamic fetch signals", () => {
350
+ const projectDir = createProject(
351
+ "js-capabilities",
352
+ [
353
+ "import fs from 'node:fs/promises';",
354
+ "import { execFile } from 'node:child_process';",
355
+ "import usb from 'usb';",
356
+ "const endpoint = process.env.API_URL;",
357
+ "await fs.readFile('config.json');",
358
+ "await fetch(endpoint);",
359
+ "await import(process.env.PLUGIN_NAME);",
360
+ "usb.getDeviceList();",
361
+ "execFile('sh', ['-c', 'echo hi']);",
362
+ ].join("\n"),
363
+ );
364
+
365
+ const analysis = analyzeJsCapabilitiesFile(join(projectDir, "index.js"));
366
+
367
+ assert.ok(analysis.capabilities.includes("fileAccess"));
368
+ assert.ok(analysis.capabilities.includes("network"));
369
+ assert.ok(analysis.capabilities.includes("hardware"));
370
+ assert.ok(analysis.capabilities.includes("childProcess"));
371
+ assert.ok(analysis.capabilities.includes("dynamicFetch"));
372
+ assert.ok(analysis.capabilities.includes("dynamicImport"));
373
+ assert.strictEqual(analysis.hasDynamicFetch, true);
374
+ assert.strictEqual(analysis.hasDynamicImport, true);
375
+ });
376
+
377
+ it("detects eval and vm-based code generation signals", () => {
378
+ const projectDir = createProject(
379
+ "js-capabilities-eval",
380
+ [
381
+ "import vm from 'node:vm';",
382
+ "eval('console.log(1)');",
383
+ "vm.runInNewContext('console.log(2)');",
384
+ ].join("\n"),
385
+ );
386
+
387
+ const analysis = analyzeJsCapabilitiesFile(join(projectDir, "index.js"));
388
+
389
+ assert.ok(analysis.capabilities.includes("codeGeneration"));
390
+ assert.strictEqual(analysis.hasEval, true);
391
+ assert.match(
392
+ (analysis.indicatorMap.codeGeneration || []).join(","),
393
+ /eval|vm\.runInNewContext/,
394
+ );
395
+ });
396
+ });
397
+
398
+ describe("analyzeJsCryptoFile()", () => {
399
+ it("detects crypto algorithms with light constant propagation", () => {
400
+ const projectDir = createProject(
401
+ "js-crypto-analysis",
402
+ [
403
+ "import { createHash, pbkdf2Sync, webcrypto } from 'node:crypto';",
404
+ "import jwt from 'jsonwebtoken';",
405
+ "import { SignJWT } from 'jose';",
406
+ "const subtle = webcrypto.subtle;",
407
+ "const digestName = 'sha256';",
408
+ "const digestOptions = { algorithm: 'RS256' };",
409
+ "const aesProfile = { name: 'AES-GCM', length: 256 };",
410
+ "const deriveProfile = { name: 'PBKDF2', hash: 'SHA-256' };",
411
+ "createHash(digestName);",
412
+ "pbkdf2Sync('secret', 'salt', 1000, 32, digestName);",
413
+ "subtle.digest('SHA-384', new Uint8Array());",
414
+ "subtle.generateKey(aesProfile, true, ['encrypt']);",
415
+ "subtle.deriveKey(deriveProfile, keyMaterial, aesProfile, false, ['encrypt']);",
416
+ "jwt.sign({ sub: '123' }, 'secret', digestOptions);",
417
+ "new SignJWT({ sub: '123' }).setProtectedHeader({ alg: 'ES256', enc: 'A256GCM' });",
418
+ ].join("\n"),
419
+ );
420
+
421
+ const analysis = analyzeJsCryptoFile(join(projectDir, "index.js"));
422
+ const names = analysis.algorithms.map((algorithm) => algorithm.name);
423
+
424
+ assert.ok(names.includes("sha256"));
425
+ assert.ok(names.includes("SHA-384"));
426
+ assert.ok(names.includes("AES-GCM"));
427
+ assert.ok(names.includes("PBKDF2"));
428
+ assert.ok(names.includes("RS256"));
429
+ assert.ok(names.includes("ES256"));
430
+ assert.ok(names.includes("A256GCM"));
431
+ assert.ok(analysis.libraries.includes("jsonwebtoken"));
432
+ assert.ok(analysis.libraries.includes("jose"));
433
+ assert.ok(analysis.libraries.includes("node:crypto"));
434
+ });
435
+
436
+ it("avoids chained-call false positives and captures signing algorithm literals", () => {
437
+ const projectDir = createProject(
438
+ "js-crypto-signing-analysis",
439
+ [
440
+ "import crypto from 'node:crypto';",
441
+ "function createSignatureBlock(payload, privateKey, alg) {",
442
+ " const hash = alg.replace('HS', 'sha');",
443
+ " const value = crypto.createHmac(hash, privateKey).update(payload, 'utf8').digest('base64url');",
444
+ " if (alg === 'Ed25519' || alg === 'Ed448') {",
445
+ " return crypto.sign(null, Buffer.from(payload, 'utf8'), { key: privateKey });",
446
+ " }",
447
+ " return value;",
448
+ "}",
449
+ "export function signBom(payload, privateKey, algorithm = 'RS512') {",
450
+ " return createSignatureBlock(payload, privateKey, algorithm);",
451
+ "}",
452
+ ].join("\n"),
453
+ );
454
+
455
+ const analysis = analyzeJsCryptoFile(join(projectDir, "index.js"));
456
+ const names = analysis.algorithms.map((algorithm) => algorithm.name);
457
+
458
+ assert.ok(names.includes("RS512"));
459
+ assert.ok(names.includes("Ed25519"));
460
+ assert.ok(names.includes("Ed448"));
461
+ assert.ok(!names.includes("base64url"));
462
+ });
463
+
464
+ it("resolves crypto values through conditional branches, fallbacks, and reassignment", () => {
465
+ const projectDir = createProject(
466
+ "js-crypto-dynamic-branches",
467
+ [
468
+ "import { createHash, createHmac, webcrypto } from 'node:crypto';",
469
+ "import jwt from 'jsonwebtoken';",
470
+ "const subtle = webcrypto.subtle;",
471
+ "let digestName = 'sha256';",
472
+ "digestName = globalThis.__preferStrongDigest ? 'sha512' : 'sha384';",
473
+ "const hmacAlgorithm = globalThis.__preferStrongMac ? 'HS512' : 'HS256';",
474
+ "const derivedHash = hmacAlgorithm.replace('HS', 'sha');",
475
+ "const cipherProfiles = globalThis.__legacyCipher",
476
+ " ? { active: { name: 'AES-CBC', length: 256 } }",
477
+ " : { active: { name: 'AES-GCM', length: 256 } };",
478
+ "const signingAlgorithm = globalThis.__legacySignature ? 'RS256' : 'RS512';",
479
+ "const jwtOptions = globalThis.__jwtOptions ?? { algorithm: signingAlgorithm };",
480
+ "createHash(digestName);",
481
+ "createHmac(derivedHash, 'secret').update('payload').digest('hex');",
482
+ "subtle.generateKey(cipherProfiles.active, true, ['encrypt', 'decrypt']);",
483
+ "jwt.sign({ sub: '123' }, 'secret', jwtOptions);",
484
+ ].join("\n"),
485
+ );
486
+
487
+ const analysis = analyzeJsCryptoFile(join(projectDir, "index.js"));
488
+ const names = analysis.algorithms.map((algorithm) => algorithm.name);
489
+
490
+ assert.ok(names.includes("sha256"));
491
+ assert.ok(names.includes("sha384"));
492
+ assert.ok(names.includes("sha512"));
493
+ assert.ok(names.includes("AES-CBC"));
494
+ assert.ok(names.includes("AES-GCM"));
495
+ assert.ok(names.includes("RS256"));
496
+ assert.ok(names.includes("RS512"));
497
+ });
498
+
499
+ it("narrows identifier values inside if-guarded crypto branches", () => {
500
+ const projectDir = createProject(
501
+ "js-crypto-if-guard-narrowing",
502
+ [
503
+ "import crypto from 'node:crypto';",
504
+ "function signPayload(payload, privateKey, alg) {",
505
+ " let hashAlg = null;",
506
+ " if (alg === 'RS256' || alg === 'RS512') {",
507
+ " hashAlg = alg.replace('RS', 'SHA');",
508
+ " return crypto.sign(hashAlg, Buffer.from(payload, 'utf8'), { key: privateKey });",
509
+ " }",
510
+ " if (alg !== 'RS384') {",
511
+ " return crypto.sign('SHA-224', Buffer.from(payload, 'utf8'), { key: privateKey });",
512
+ " } else {",
513
+ " hashAlg = alg.replace('RS', 'SHA');",
514
+ " return crypto.sign(hashAlg, Buffer.from(payload, 'utf8'), { key: privateKey });",
515
+ " }",
516
+ "}",
517
+ ].join("\n"),
518
+ );
519
+
520
+ const analysis = analyzeJsCryptoFile(join(projectDir, "index.js"));
521
+ const names = analysis.algorithms.map((algorithm) => algorithm.name);
522
+ const signAlgorithms = analysis.algorithms
523
+ .filter((algorithm) => algorithm.source === "node:crypto.sign")
524
+ .map((algorithm) => algorithm.name);
525
+
526
+ assert.ok(names.includes("RS256"));
527
+ assert.ok(names.includes("RS512"));
528
+ assert.ok(signAlgorithms.includes("SHA256"));
529
+ assert.ok(signAlgorithms.includes("SHA512"));
530
+ assert.ok(signAlgorithms.includes("SHA384"));
531
+ assert.ok(signAlgorithms.includes("SHA-224"));
532
+ });
533
+
534
+ it("narrows identifier values inside switch/case crypto branches", () => {
535
+ const projectDir = createProject(
536
+ "js-crypto-switch-guard-narrowing",
537
+ [
538
+ "import crypto from 'node:crypto';",
539
+ "function signPayloadWithSwitch(payload, privateKey, alg) {",
540
+ " switch (alg) {",
541
+ " case 'RS256':",
542
+ " case 'RS512':",
543
+ " return crypto.sign(alg.replace('RS', 'SHA'), Buffer.from(payload, 'utf8'), { key: privateKey });",
544
+ " case 'RS384':",
545
+ " return crypto.sign(alg.replace('RS', 'SHA'), Buffer.from(payload, 'utf8'), { key: privateKey });",
546
+ " default:",
547
+ " return crypto.sign('SHA-224', Buffer.from(payload, 'utf8'), { key: privateKey });",
548
+ " }",
549
+ "}",
550
+ ].join("\n"),
551
+ );
552
+
553
+ const analysis = analyzeJsCryptoFile(join(projectDir, "index.js"));
554
+ const signAlgorithms = analysis.algorithms
555
+ .filter((algorithm) => algorithm.source === "node:crypto.sign")
556
+ .map((algorithm) => algorithm.name);
557
+
558
+ assert.ok(signAlgorithms.includes("SHA256"));
559
+ assert.ok(signAlgorithms.includes("SHA512"));
560
+ assert.ok(signAlgorithms.includes("SHA384"));
561
+ assert.ok(signAlgorithms.includes("SHA-224"));
562
+ });
563
+
564
+ it("narrows switch default branches using a known finite identifier union", () => {
565
+ const projectDir = createProject(
566
+ "js-crypto-switch-default-narrowing",
567
+ [
568
+ "import crypto from 'node:crypto';",
569
+ "function signPayloadWithSwitchDefault(payload, privateKey) {",
570
+ " const alg = globalThis.__preferLegacy ? 'RS256' : 'RS384';",
571
+ " switch (alg) {",
572
+ " case 'RS256':",
573
+ " return crypto.sign(alg.replace('RS', 'SHA'), Buffer.from(payload, 'utf8'), { key: privateKey });",
574
+ " default:",
575
+ " return crypto.sign(alg.replace('RS', 'SHA'), Buffer.from(payload, 'utf8'), { key: privateKey });",
576
+ " }",
577
+ "}",
578
+ ].join("\n"),
579
+ );
580
+
581
+ const analysis = analyzeJsCryptoFile(join(projectDir, "index.js"));
582
+ const signAlgorithms = analysis.algorithms
583
+ .filter((algorithm) => algorithm.source === "node:crypto.sign")
584
+ .map((algorithm) => algorithm.name);
585
+
586
+ assert.ok(signAlgorithms.includes("SHA256"));
587
+ assert.ok(signAlgorithms.includes("SHA384"));
588
+ assert.ok(!signAlgorithms.includes("SHA512"));
589
+ });
590
+ });
591
+
592
+ describe("detectJsCryptoInventory()", () => {
593
+ it("aggregates crypto algorithm usage across source files", async () => {
594
+ const projectDir = createProjectFiles("js-crypto-inventory", {
595
+ "src/hash.js": [
596
+ "import { createHash } from 'node:crypto';",
597
+ "const algo = 'sha512';",
598
+ "createHash(algo);",
599
+ ].join("\n"),
600
+ "src/webcrypto.js": [
601
+ "const profile = { name: 'AES-GCM', length: 256 };",
602
+ "crypto.subtle.generateKey(profile, true, ['encrypt']);",
603
+ ].join("\n"),
604
+ });
605
+
606
+ const inventory = await detectJsCryptoInventory(projectDir, false);
607
+ const names = inventory.algorithms.map((algorithm) => algorithm.name);
608
+ const files = inventory.algorithms.map((algorithm) =>
609
+ normalizePathForAssertion(algorithm.fileName),
610
+ );
611
+
612
+ assert.ok(names.includes("sha512"));
613
+ assert.ok(names.includes("AES-GCM"));
614
+ assert.ok(files.includes("src/hash.js"));
615
+ assert.ok(files.includes("src/webcrypto.js"));
616
+ });
617
+
618
+ it("honors exclude globs during crypto inventory collection", async () => {
619
+ const projectDir = createProjectFiles("js-crypto-inventory-excludes", {
620
+ "src/hash.js": [
621
+ "import { createHash } from 'node:crypto';",
622
+ "createHash('sha256');",
623
+ ].join("\n"),
624
+ "test/ignored.js": [
625
+ "import { createHash } from 'node:crypto';",
626
+ "createHash('sha512');",
627
+ ].join("\n"),
628
+ "node_modules/demo/index.js": [
629
+ "import { createHash } from 'node:crypto';",
630
+ "createHash('sha1');",
631
+ ].join("\n"),
632
+ });
633
+
634
+ const inventory = await detectJsCryptoInventory(projectDir, {
635
+ deep: true,
636
+ exclude: ["**/test/**", "**/node_modules/**"],
637
+ });
638
+ const names = inventory.algorithms.map((algorithm) => algorithm.name);
639
+
640
+ assert.ok(names.includes("sha256"));
641
+ assert.equal(names.includes("sha512"), false);
642
+ assert.equal(names.includes("sha1"), false);
643
+ });
644
+ });
645
+
327
646
  describe("detectMcpInventory()", () => {
647
+ it("honors exclude globs during MCP source discovery", () => {
648
+ const projectDir = createProjectFiles("mcp-with-excludes", {
649
+ "src/server.js": [
650
+ "import { McpServer } from '@modelcontextprotocol/server';",
651
+ "const server = new McpServer({ name: 'included-server', version: '1.0.0' });",
652
+ "void server;",
653
+ ].join("\n"),
654
+ "test/ignored.js": [
655
+ "import { McpServer } from '@modelcontextprotocol/server';",
656
+ "const server = new McpServer({ name: 'ignored-server', version: '9.9.9' });",
657
+ "void server;",
658
+ ].join("\n"),
659
+ });
660
+
661
+ const inventory = detectMcpInventory(projectDir, {
662
+ deep: true,
663
+ exclude: ["**/test/**"],
664
+ });
665
+
666
+ assert.strictEqual(inventory.services.length, 1);
667
+ assert.strictEqual(inventory.services[0].name, "included-server");
668
+ });
669
+
328
670
  it("detects an official authenticated streamable HTTP MCP server", () => {
329
671
  const projectDir = createProjectFiles("mcp-http-server", {
330
672
  "src/server.js": [