@cyclonedx/cdxgen 12.1.4 → 12.2.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 (184) hide show
  1. package/README.md +47 -39
  2. package/bin/cdxgen.js +181 -90
  3. package/bin/evinse.js +4 -4
  4. package/bin/repl.js +3 -3
  5. package/bin/sign.js +102 -0
  6. package/bin/validate.js +233 -0
  7. package/bin/verify.js +69 -28
  8. package/data/queries.json +1 -1
  9. package/data/rules/ci-permissions.yaml +186 -0
  10. package/data/rules/dependency-sources.yaml +123 -0
  11. package/data/rules/package-integrity.yaml +135 -0
  12. package/data/rules/vscode-extensions.yaml +228 -0
  13. package/lib/cli/index.js +484 -440
  14. package/lib/evinser/db.js +137 -0
  15. package/lib/{helpers → evinser}/db.poku.js +2 -6
  16. package/lib/evinser/evinser.js +5 -18
  17. package/lib/evinser/swiftsem.js +1 -1
  18. package/lib/helpers/bomSigner.js +312 -0
  19. package/lib/helpers/bomSigner.poku.js +156 -0
  20. package/lib/helpers/caxa.js +1 -1
  21. package/lib/helpers/ciParsers/azurePipelines.js +295 -0
  22. package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
  23. package/lib/helpers/ciParsers/circleCi.js +286 -0
  24. package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
  25. package/lib/helpers/ciParsers/common.js +24 -0
  26. package/lib/helpers/ciParsers/githubActions.js +636 -0
  27. package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
  28. package/lib/helpers/ciParsers/gitlabCi.js +213 -0
  29. package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
  30. package/lib/helpers/ciParsers/jenkins.js +181 -0
  31. package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
  32. package/lib/helpers/depsUtils.js +203 -0
  33. package/lib/helpers/depsUtils.poku.js +150 -0
  34. package/lib/helpers/display.js +429 -14
  35. package/lib/helpers/envcontext.js +23 -8
  36. package/lib/helpers/formulationParsers.js +351 -0
  37. package/lib/helpers/logger.js +14 -0
  38. package/lib/helpers/protobom.js +9 -9
  39. package/lib/helpers/pythonutils.js +305 -0
  40. package/lib/helpers/pythonutils.poku.js +469 -0
  41. package/lib/helpers/utils.js +970 -528
  42. package/lib/helpers/utils.poku.js +139 -256
  43. package/lib/helpers/versutils.js +202 -0
  44. package/lib/helpers/versutils.poku.js +315 -0
  45. package/lib/helpers/vsixutils.js +1061 -0
  46. package/lib/helpers/vsixutils.poku.js +2247 -0
  47. package/lib/managers/binary.js +19 -19
  48. package/lib/managers/docker.js +108 -1
  49. package/lib/managers/oci.js +10 -0
  50. package/lib/managers/piptree.js +4 -10
  51. package/lib/parsers/npmrc.js +92 -0
  52. package/lib/parsers/npmrc.poku.js +528 -0
  53. package/lib/server/openapi.yaml +1 -10
  54. package/lib/server/server.js +58 -16
  55. package/lib/server/server.poku.js +123 -144
  56. package/lib/stages/postgen/annotator.js +1 -1
  57. package/lib/stages/postgen/auditBom.js +197 -0
  58. package/lib/stages/postgen/auditBom.poku.js +378 -0
  59. package/lib/stages/postgen/postgen.js +54 -1
  60. package/lib/stages/postgen/postgen.poku.js +90 -1
  61. package/lib/stages/postgen/ruleEngine.js +369 -0
  62. package/lib/stages/pregen/envAudit.js +299 -0
  63. package/lib/stages/pregen/envAudit.poku.js +572 -0
  64. package/lib/stages/pregen/pregen.js +12 -8
  65. package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
  66. package/lib/third-party/arborist/lib/node.js +3 -3
  67. package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
  68. package/lib/third-party/arborist/lib/tree-check.js +1 -1
  69. package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
  70. package/lib/validator/complianceEngine.js +241 -0
  71. package/lib/validator/complianceEngine.poku.js +168 -0
  72. package/lib/validator/complianceRules.js +1610 -0
  73. package/lib/validator/complianceRules.poku.js +328 -0
  74. package/lib/validator/index.js +222 -0
  75. package/lib/validator/index.poku.js +144 -0
  76. package/lib/validator/reporters/annotations.js +121 -0
  77. package/lib/validator/reporters/console.js +149 -0
  78. package/lib/validator/reporters/index.js +41 -0
  79. package/lib/validator/reporters/json.js +37 -0
  80. package/lib/validator/reporters/sarif.js +184 -0
  81. package/lib/validator/reporters.poku.js +150 -0
  82. package/package.json +8 -8
  83. package/types/bin/sign.d.ts +3 -0
  84. package/types/bin/sign.d.ts.map +1 -0
  85. package/types/bin/validate.d.ts +3 -0
  86. package/types/bin/validate.d.ts.map +1 -0
  87. package/types/helpers/utils.d.ts +0 -1
  88. package/types/lib/cli/index.d.ts +49 -52
  89. package/types/lib/cli/index.d.ts.map +1 -1
  90. package/types/lib/evinser/db.d.ts +34 -0
  91. package/types/lib/evinser/db.d.ts.map +1 -0
  92. package/types/lib/evinser/evinser.d.ts +63 -16
  93. package/types/lib/evinser/evinser.d.ts.map +1 -1
  94. package/types/lib/helpers/bomSigner.d.ts +27 -0
  95. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  96. package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
  97. package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
  98. package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
  99. package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
  100. package/types/lib/helpers/ciParsers/common.d.ts +11 -0
  101. package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
  102. package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
  103. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
  104. package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
  105. package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
  106. package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
  107. package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
  108. package/types/lib/helpers/depsUtils.d.ts +21 -0
  109. package/types/lib/helpers/depsUtils.d.ts.map +1 -0
  110. package/types/lib/helpers/display.d.ts +111 -11
  111. package/types/lib/helpers/display.d.ts.map +1 -1
  112. package/types/lib/helpers/envcontext.d.ts +19 -7
  113. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  114. package/types/lib/helpers/formulationParsers.d.ts +50 -0
  115. package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
  116. package/types/lib/helpers/logger.d.ts +15 -1
  117. package/types/lib/helpers/logger.d.ts.map +1 -1
  118. package/types/lib/helpers/protobom.d.ts +2 -2
  119. package/types/lib/helpers/pythonutils.d.ts +18 -0
  120. package/types/lib/helpers/pythonutils.d.ts.map +1 -0
  121. package/types/lib/helpers/utils.d.ts +532 -128
  122. package/types/lib/helpers/utils.d.ts.map +1 -1
  123. package/types/lib/helpers/versutils.d.ts +8 -0
  124. package/types/lib/helpers/versutils.d.ts.map +1 -0
  125. package/types/lib/helpers/vsixutils.d.ts +130 -0
  126. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  127. package/types/lib/managers/docker.d.ts +12 -31
  128. package/types/lib/managers/docker.d.ts.map +1 -1
  129. package/types/lib/managers/oci.d.ts +11 -1
  130. package/types/lib/managers/oci.d.ts.map +1 -1
  131. package/types/lib/managers/piptree.d.ts.map +1 -1
  132. package/types/lib/parsers/npmrc.d.ts +26 -0
  133. package/types/lib/parsers/npmrc.d.ts.map +1 -0
  134. package/types/lib/server/server.d.ts +21 -2
  135. package/types/lib/server/server.d.ts.map +1 -1
  136. package/types/lib/stages/postgen/auditBom.d.ts +20 -0
  137. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  138. package/types/lib/stages/postgen/postgen.d.ts +8 -1
  139. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  140. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  141. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  142. package/types/lib/stages/pregen/envAudit.d.ts +8 -0
  143. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  144. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  145. package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
  146. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  147. package/types/lib/validator/complianceEngine.d.ts +66 -0
  148. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  149. package/types/lib/validator/complianceRules.d.ts +70 -0
  150. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  151. package/types/lib/validator/index.d.ts +70 -0
  152. package/types/lib/validator/index.d.ts.map +1 -0
  153. package/types/lib/validator/reporters/annotations.d.ts +31 -0
  154. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  155. package/types/lib/validator/reporters/console.d.ts +30 -0
  156. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  157. package/types/lib/validator/reporters/index.d.ts +21 -0
  158. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  159. package/types/lib/validator/reporters/json.d.ts +11 -0
  160. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  161. package/types/lib/validator/reporters/sarif.d.ts +16 -0
  162. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  163. package/lib/helpers/db.js +0 -162
  164. package/types/helpers/db.d.ts +0 -35
  165. package/types/helpers/db.d.ts.map +0 -1
  166. package/types/lib/helpers/db.d.ts +0 -35
  167. package/types/lib/helpers/db.d.ts.map +0 -1
  168. package/types/lib/helpers/validator.d.ts.map +0 -1
  169. package/types/managers/binary.d.ts +0 -37
  170. package/types/managers/binary.d.ts.map +0 -1
  171. package/types/managers/docker.d.ts +0 -56
  172. package/types/managers/docker.d.ts.map +0 -1
  173. package/types/managers/oci.d.ts +0 -2
  174. package/types/managers/oci.d.ts.map +0 -1
  175. package/types/managers/piptree.d.ts +0 -2
  176. package/types/managers/piptree.d.ts.map +0 -1
  177. package/types/server/server.d.ts +0 -34
  178. package/types/server/server.d.ts.map +0 -1
  179. package/types/stages/postgen/annotator.d.ts +0 -27
  180. package/types/stages/postgen/annotator.d.ts.map +0 -1
  181. package/types/stages/postgen/postgen.d.ts +0 -51
  182. package/types/stages/postgen/postgen.d.ts.map +0 -1
  183. package/types/stages/pregen/pregen.d.ts +0 -59
  184. package/types/stages/pregen/pregen.d.ts.map +0 -1
@@ -48,7 +48,6 @@ const ALLOWED_PARAMS = [
48
48
  "deep",
49
49
  "profile",
50
50
  "exclude",
51
- "includeFormulation",
52
51
  "includeCrypto",
53
52
  "standard",
54
53
  "minConfidence",
@@ -267,6 +266,10 @@ function gitClone(repoUrl, branch = null) {
267
266
  const gitArgs = [
268
267
  "-c",
269
268
  "alias.clone=",
269
+ "-c",
270
+ "core.fsmonitor=false",
271
+ "-c",
272
+ "safe.bareRepository=explicit",
270
273
  "clone",
271
274
  repoUrl,
272
275
  "--depth",
@@ -288,16 +291,26 @@ function gitClone(repoUrl, branch = null) {
288
291
  `Cloning Repo${branch ? ` with branch ${branch}` : ""} to ${tempDir}`,
289
292
  );
290
293
  const gitAllowProtocol = getGitAllowProtocol();
291
- // See issue #1956
294
+ const envConfigs = {
295
+ GIT_CONFIG_COUNT: "2",
296
+ GIT_CONFIG_KEY_0: "core.fsmonitor",
297
+ GIT_CONFIG_VALUE_0: "false",
298
+ GIT_CONFIG_KEY_1: "safe.bareRepository",
299
+ GIT_CONFIG_VALUE_1: "explicit",
300
+ };
292
301
  const env = isSecureMode
293
302
  ? {
294
303
  ...process.env,
304
+ ...envConfigs,
295
305
  GIT_CONFIG_NOSYSTEM: "1",
296
306
  GIT_CONFIG_NOGLOBAL: "1",
297
307
  GIT_ALLOW_PROTOCOL: gitAllowProtocol,
298
308
  }
299
- : { ...process.env, GIT_ALLOW_PROTOCOL: gitAllowProtocol };
300
-
309
+ : {
310
+ ...process.env,
311
+ ...envConfigs,
312
+ GIT_ALLOW_PROTOCOL: gitAllowProtocol,
313
+ };
301
314
  const result = safeSpawnSync("git", gitArgs, {
302
315
  shell: false,
303
316
  env,
@@ -359,6 +372,17 @@ export function parseValue(raw) {
359
372
  throw new TypeError(`Invalid value type: ${t}.`);
360
373
  }
361
374
 
375
+ /**
376
+ * Parses allowed query/body parameters into a typed options object.
377
+ * Query parameters take priority over body parameters. Handles the
378
+ * `type` → `projectType` rename, lifecycle-based `installDeps` defaulting,
379
+ * and profile option expansion.
380
+ *
381
+ * @param {Object} q Parsed query string key/value map
382
+ * @param {Object} [body={}] Parsed request body key/value map
383
+ * @param {Object} [options={}] Seed options object to merge results into
384
+ * @returns {Object} Populated options object
385
+ */
362
386
  export function parseQueryString(q, body = {}, options = {}) {
363
387
  // Priority is query params followed by body
364
388
  for (const param of ALLOWED_PARAMS) {
@@ -378,9 +402,17 @@ export function parseQueryString(q, body = {}, options = {}) {
378
402
  return options;
379
403
  }
380
404
 
405
+ /**
406
+ * Extracts query parameters from an incoming HTTP request object.
407
+ * Handles repeated keys by collecting their values into an array.
408
+ * Returns an empty object if the URL cannot be parsed.
409
+ *
410
+ * @param {Object} req Node.js/connect HTTP request object
411
+ * @returns {Object} Key/value map of query parameters from the request URL
412
+ */
381
413
  export function getQueryParams(req) {
382
414
  try {
383
- if (!req || !req.url) {
415
+ if (!req?.url) {
384
416
  return {};
385
417
  }
386
418
 
@@ -389,7 +421,7 @@ export function getQueryParams(req) {
389
421
  const baseUrl = `${protocol}://${host}`;
390
422
 
391
423
  const fullUrl = new URL(req.url, baseUrl);
392
- const params = {};
424
+ const params = Object.create(null);
393
425
 
394
426
  // Convert multiple values to an array
395
427
  for (const [key, value] of fullUrl.searchParams) {
@@ -436,21 +468,25 @@ const configureServer = (cdxgenServer) => {
436
468
  const ALL_INTERFACES = new Set(["0.0.0.0", "::", "::/128", "::/0"]);
437
469
 
438
470
  const start = (options) => {
471
+ if (isSecureMode && !process.permission) {
472
+ console.error(
473
+ "SECURE MODE: Node.js permission model not enabled. Use --permission flag.",
474
+ );
475
+ process.exit(1);
476
+ }
439
477
  console.log(`cdxgen server version ${CDXGEN_VERSION}`);
440
-
441
- console.log(
442
- "Listening on",
443
- options.serverHost,
444
- options.serverPort,
445
- "without authentication!",
446
- );
447
478
  if (ALL_INTERFACES.has(options.serverHost)) {
448
479
  console.log("Exposing cdxgen server on all IP address is a security risk!");
449
480
  if (isSecureMode) {
450
481
  process.exit(1);
451
482
  }
452
483
  }
453
- if (+options.serverPort < 1024) {
484
+ const serverPort = Number(options.serverPort);
485
+ if (!Number.isInteger(serverPort) || serverPort <= 0 || serverPort > 65535) {
486
+ console.log("Invalid server port specified.");
487
+ process.exit(1);
488
+ }
489
+ if (serverPort < 1024) {
454
490
  console.log(
455
491
  "Running cdxgen server with a privileged port is a security risk!",
456
492
  );
@@ -490,9 +526,15 @@ const start = (options) => {
490
526
  process.exit(1);
491
527
  }
492
528
  }
529
+ console.log(
530
+ "Listening on",
531
+ options.serverHost,
532
+ serverPort,
533
+ "without authentication!",
534
+ );
493
535
  const cdxgenServer = http
494
536
  .createServer(app)
495
- .listen(options.serverPort, options.serverHost);
537
+ .listen(serverPort, options.serverHost);
496
538
  configureServer(cdxgenServer);
497
539
 
498
540
  app.use("/health", (_req, res) => {
@@ -512,7 +554,7 @@ const start = (options) => {
512
554
  }
513
555
  const q = getQueryParams(req);
514
556
  let cleanup = false;
515
- let reqOptions = {};
557
+ let reqOptions = Object.create(null);
516
558
  try {
517
559
  reqOptions = parseQueryString(
518
560
  q,
@@ -11,53 +11,71 @@ import {
11
11
  validateAndRejectGitSource,
12
12
  } from "./server.js";
13
13
 
14
+ function nullProtoObj(obj) {
15
+ if (obj === null || typeof obj !== "object") {
16
+ return obj;
17
+ }
18
+ if (Array.isArray(obj)) {
19
+ return obj.map(nullProtoObj);
20
+ }
21
+ if (Object.prototype.toString.call(obj) === "[object Object]") {
22
+ const result = Object.create(null);
23
+ for (const [key, value] of Object.entries(obj)) {
24
+ result[key] = nullProtoObj(value);
25
+ }
26
+ return result;
27
+ }
28
+ return obj;
29
+ }
30
+
31
+ function checkEqual(actual, expected, message) {
32
+ assert.deepStrictEqual(nullProtoObj(actual), nullProtoObj(expected), message);
33
+ }
34
+
14
35
  it("parseValue tests", () => {
15
- assert.deepStrictEqual(parseValue("foo"), "foo");
16
- assert.deepStrictEqual(parseValue("foo\n"), "foo");
17
- assert.deepStrictEqual(parseValue("foo\r\n"), "foo");
18
- assert.deepStrictEqual(parseValue(1), 1);
19
- assert.deepStrictEqual(parseValue("true"), true);
20
- assert.deepStrictEqual(parseValue("false"), false);
21
- assert.deepStrictEqual(parseValue(["foo", "bar", 42]), ["foo", "bar", 42]);
36
+ checkEqual(parseValue("foo"), "foo");
37
+ checkEqual(parseValue("foo\n"), "foo");
38
+ checkEqual(parseValue("foo\r\n"), "foo");
39
+ checkEqual(parseValue(1), 1);
40
+ checkEqual(parseValue("true"), true);
41
+ checkEqual(parseValue("false"), false);
42
+ checkEqual(parseValue(["foo", "bar", 42]), ["foo", "bar", 42]);
22
43
  assert.throws(() => parseValue({ foo: "bar" }), TypeError);
23
44
  assert.throws(() => parseValue([42, "foo", { foo: "bar" }]), TypeError);
24
45
  assert.throws(() => parseValue([42, "foo", new Error()]), TypeError);
25
46
  assert.throws(() => parseValue(["foo", "bar", new String(42)]), TypeError);
26
- assert.deepStrictEqual(parseValue(true), true);
27
- assert.deepStrictEqual(parseValue(false), false);
28
- assert.deepStrictEqual(parseValue(null), null);
29
- assert.deepStrictEqual(parseValue(undefined), undefined);
30
- assert.deepStrictEqual(parseValue([null, undefined, null]), [
47
+ checkEqual(parseValue(true), true);
48
+ checkEqual(parseValue(false), false);
49
+ checkEqual(parseValue(null), null);
50
+ checkEqual(parseValue(undefined), undefined);
51
+ checkEqual(parseValue([null, undefined, null]), [null, undefined, null]);
52
+ checkEqual(parseValue(""), "");
53
+ checkEqual(parseValue(" \n"), " ");
54
+ checkEqual(parseValue("42"), "42");
55
+ checkEqual(parseValue("0"), "0");
56
+ checkEqual(parseValue("-1"), "-1");
57
+ checkEqual(parseValue("True"), "True");
58
+ checkEqual(parseValue("False"), "False");
59
+ checkEqual(parseValue(" TRUE "), " TRUE ");
60
+ checkEqual(parseValue(["true", "false", 0, "0", null, undefined]), [
61
+ true,
62
+ false,
63
+ 0,
64
+ "0",
31
65
  null,
32
66
  undefined,
33
- null,
34
67
  ]);
35
- assert.deepStrictEqual(parseValue(""), "");
36
- assert.deepStrictEqual(parseValue(" \n"), " ");
37
- assert.deepStrictEqual(parseValue("42"), "42");
38
- assert.deepStrictEqual(parseValue("0"), "0");
39
- assert.deepStrictEqual(parseValue("-1"), "-1");
40
- assert.deepStrictEqual(parseValue("True"), "True");
41
- assert.deepStrictEqual(parseValue("False"), "False");
42
- assert.deepStrictEqual(parseValue(" TRUE "), " TRUE ");
43
- assert.deepStrictEqual(
44
- parseValue(["true", "false", 0, "0", null, undefined]),
45
- [true, false, 0, "0", null, undefined],
46
- );
47
68
  assert.throws(() => parseValue([["nested"]]), TypeError);
48
69
  assert.throws(() => parseValue(Symbol("test")), TypeError);
49
70
  assert.throws(() => parseValue(BigInt(42)), TypeError);
50
71
  // biome-ignore-start lint/suspicious/noEmptyBlockStatements: test
51
72
  assert.throws(() => parseValue(() => {}), TypeError);
52
73
  // biome-ignore-end lint/suspicious/noEmptyBlockStatements: test
53
- assert.deepStrictEqual(parseValue(Number.NaN), Number.NaN);
54
- assert.deepStrictEqual(
55
- parseValue(Number.POSITIVE_INFINITY),
56
- Number.POSITIVE_INFINITY,
57
- );
74
+ checkEqual(parseValue(Number.NaN), Number.NaN);
75
+ checkEqual(parseValue(Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
58
76
  const obj = { toString: () => "foo" };
59
77
  assert.throws(() => parseValue(obj), TypeError);
60
- assert.deepStrictEqual(parseValue("hello\r\n"), "hello");
78
+ checkEqual(parseValue("hello\r\n"), "hello");
61
79
  });
62
80
 
63
81
  describe("parseQueryString tests", () => {
@@ -70,22 +88,22 @@ describe("parseQueryString tests", () => {
70
88
  };
71
89
  const options = {};
72
90
  const result = parseQueryString(q, body, options);
73
- assert.deepStrictEqual(result.foo, undefined);
74
- assert.deepStrictEqual(result.excludeType, ["2"]);
75
- assert.deepStrictEqual(result.technique, ["manifest-analysis"]);
91
+ checkEqual(result.foo, undefined);
92
+ checkEqual(result.excludeType, ["2"]);
93
+ checkEqual(result.technique, ["manifest-analysis"]);
76
94
  });
77
95
 
78
96
  it("splits type into projectType and removes type", () => {
79
97
  const options = { type: "a,b,c" };
80
98
  const result = parseQueryString({}, {}, options);
81
- assert.deepStrictEqual(result.projectType, ["a", "b", "c"]);
82
- assert.deepStrictEqual(result.type, undefined);
99
+ checkEqual(result.projectType, ["a", "b", "c"]);
100
+ checkEqual(result.type, undefined);
83
101
  });
84
102
 
85
103
  it("sets installDeps to false for pre-build lifecycle", () => {
86
104
  const options = { lifecycle: "pre-build" };
87
105
  const result = parseQueryString({}, {}, options);
88
- assert.deepStrictEqual(result.installDeps, false);
106
+ checkEqual(result.installDeps, false);
89
107
  });
90
108
  });
91
109
 
@@ -102,23 +120,23 @@ describe("isAllowedHost()", () => {
102
120
 
103
121
  it("returns true if CDXGEN_SERVER_ALLOWED_HOSTS is not set", () => {
104
122
  delete process.env.CDXGEN_SERVER_ALLOWED_HOSTS;
105
- assert.deepStrictEqual(isAllowedHost("anything"), true);
123
+ checkEqual(isAllowedHost("anything"), true);
106
124
  });
107
125
 
108
126
  it("returns true for a hostname that is in the list", () => {
109
127
  process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "foo.com,bar.com";
110
- assert.deepStrictEqual(isAllowedHost("foo.com"), true);
111
- assert.deepStrictEqual(isAllowedHost("bar.com"), true);
128
+ checkEqual(isAllowedHost("foo.com"), true);
129
+ checkEqual(isAllowedHost("bar.com"), true);
112
130
  });
113
131
 
114
132
  it("returns false for a hostname not in the list", () => {
115
133
  process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "foo.com,bar.com";
116
- assert.deepStrictEqual(isAllowedHost("baz.com"), false);
134
+ checkEqual(isAllowedHost("baz.com"), false);
117
135
  });
118
136
 
119
137
  it("treats an empty-string env var as unset (returns true)", () => {
120
138
  process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "";
121
- assert.deepStrictEqual(isAllowedHost("whatever"), true);
139
+ checkEqual(isAllowedHost("whatever"), true);
122
140
  });
123
141
  });
124
142
 
@@ -203,15 +221,15 @@ describe("isAllowedPath()", () => {
203
221
  describe("isAllowedWinPath windows tests()", () => {
204
222
  it("returns false for windows device name paths", () => {
205
223
  if (isWin) {
206
- assert.deepStrictEqual(isAllowedWinPath("CON:../foo"), false);
207
- assert.deepStrictEqual(isAllowedWinPath("X:\\foo\\..\\bar"), true);
208
- assert.deepStrictEqual(isAllowedWinPath("C:\\Users"), true);
209
- assert.deepStrictEqual(isAllowedWinPath("C:\\🚀"), true);
210
- assert.deepStrictEqual(isAllowedWinPath("C:"), true);
211
- assert.deepStrictEqual(isAllowedWinPath("c:"), true);
212
- assert.deepStrictEqual(isAllowedWinPath("CON:"), false);
213
- assert.deepStrictEqual(isAllowedWinPath("COM¹:"), false);
214
- assert.deepStrictEqual(isAllowedWinPath("COM¹:../foo"), false);
224
+ checkEqual(isAllowedWinPath("CON:../foo"), false);
225
+ checkEqual(isAllowedWinPath("X:\\foo\\..\\bar"), true);
226
+ checkEqual(isAllowedWinPath("C:\\Users"), true);
227
+ checkEqual(isAllowedWinPath("C:\\🚀"), true);
228
+ checkEqual(isAllowedWinPath("C:"), true);
229
+ checkEqual(isAllowedWinPath("c:"), true);
230
+ checkEqual(isAllowedWinPath("CON:"), false);
231
+ checkEqual(isAllowedWinPath("COM¹:"), false);
232
+ checkEqual(isAllowedWinPath("COM¹:../foo"), false);
215
233
  for (const d of [
216
234
  "PRN:.\\..\\bar",
217
235
  "LpT5:/another/path",
@@ -244,7 +262,7 @@ describe("isAllowedWinPath windows tests()", () => {
244
262
  "🚀:\\",
245
263
  "⚡:\\",
246
264
  ]) {
247
- assert.deepStrictEqual(isAllowedWinPath(d), false);
265
+ checkEqual(isAllowedWinPath(d), false);
248
266
  }
249
267
  }
250
268
  });
@@ -264,7 +282,7 @@ describe("getQueryParams", () => {
264
282
  );
265
283
  const result = getQueryParams(req);
266
284
 
267
- assert.deepStrictEqual(result, {
285
+ checkEqual(result, {
268
286
  url: "https://example.com",
269
287
  multiProject: "true",
270
288
  type: "js",
@@ -277,7 +295,7 @@ describe("getQueryParams", () => {
277
295
  );
278
296
  const result = getQueryParams(req);
279
297
 
280
- assert.deepStrictEqual(result, {
298
+ checkEqual(result, {
281
299
  q: "hello world",
282
300
  filter: "category=tech",
283
301
  });
@@ -288,7 +306,7 @@ describe("getQueryParams", () => {
288
306
  const result = getQueryParams(req);
289
307
 
290
308
  // URLSearchParams.entries() returns the first value when there are duplicates
291
- assert.deepStrictEqual(result, {
309
+ checkEqual(result, {
292
310
  tags: ["javascript", "react", "node"],
293
311
  });
294
312
  });
@@ -297,21 +315,21 @@ describe("getQueryParams", () => {
297
315
  const req = createMockRequest("/sbom");
298
316
  const result = getQueryParams(req);
299
317
 
300
- assert.deepStrictEqual(result, {});
318
+ checkEqual(result, {});
301
319
  });
302
320
 
303
321
  it("should handle query string with only question mark", () => {
304
322
  const req = createMockRequest("/sbom?");
305
323
  const result = getQueryParams(req);
306
324
 
307
- assert.deepStrictEqual(result, {});
325
+ checkEqual(result, {});
308
326
  });
309
327
 
310
328
  it("should handle parameters without values", () => {
311
329
  const req = createMockRequest("/api?flag1&flag2&param=value");
312
330
  const result = getQueryParams(req);
313
331
 
314
- assert.deepStrictEqual(result, {
332
+ checkEqual(result, {
315
333
  flag1: "",
316
334
  flag2: "",
317
335
  param: "value",
@@ -325,7 +343,7 @@ describe("getQueryParams", () => {
325
343
  );
326
344
  const result = getQueryParams(req);
327
345
 
328
- assert.deepStrictEqual(result, {
346
+ checkEqual(result, {
329
347
  param1: "value1",
330
348
  });
331
349
  });
@@ -338,7 +356,7 @@ describe("getQueryParams", () => {
338
356
  );
339
357
  const result = getQueryParams(req);
340
358
 
341
- assert.deepStrictEqual(result, {
359
+ checkEqual(result, {
342
360
  token: "abc123",
343
361
  });
344
362
  });
@@ -349,7 +367,7 @@ describe("getQueryParams", () => {
349
367
  );
350
368
  const result = getQueryParams(req);
351
369
 
352
- assert.deepStrictEqual(result, {
370
+ checkEqual(result, {
353
371
  name: "john",
354
372
  age: "25",
355
373
  active: "true",
@@ -362,7 +380,7 @@ describe("getQueryParams", () => {
362
380
  );
363
381
  const result = getQueryParams(req);
364
382
 
365
- assert.deepStrictEqual(result, {
383
+ checkEqual(result, {
366
384
  q: "hello world!",
367
385
  category: "web development",
368
386
  });
@@ -372,14 +390,14 @@ describe("getQueryParams", () => {
372
390
  const req = createMockRequest(undefined);
373
391
  const result = getQueryParams(req);
374
392
 
375
- assert.deepStrictEqual(result, {});
393
+ checkEqual(result, {});
376
394
  });
377
395
 
378
396
  it("should handle numeric values as strings", () => {
379
397
  const req = createMockRequest("/calculate?x=10&y=20&operation=add");
380
398
  const result = getQueryParams(req);
381
399
 
382
- assert.deepStrictEqual(result, {
400
+ checkEqual(result, {
383
401
  x: "10",
384
402
  y: "20",
385
403
  operation: "add",
@@ -390,7 +408,7 @@ describe("getQueryParams", () => {
390
408
  const req = createMockRequest("/config?debug=true&verbose=false&enabled=1");
391
409
  const result = getQueryParams(req);
392
410
 
393
- assert.deepStrictEqual(result, {
411
+ checkEqual(result, {
394
412
  debug: "true",
395
413
  verbose: "false",
396
414
  enabled: "1",
@@ -402,7 +420,7 @@ describe("getQueryParams", () => {
402
420
  const req = createMockRequest("not-a-valid-url");
403
421
  const result = getQueryParams(req);
404
422
 
405
- assert.deepStrictEqual(result, {});
423
+ checkEqual(result, {});
406
424
  });
407
425
 
408
426
  it("should handle empty host gracefully", () => {
@@ -413,7 +431,7 @@ describe("getQueryParams", () => {
413
431
  };
414
432
  const result = getQueryParams(req);
415
433
 
416
- assert.deepStrictEqual(result, {
434
+ checkEqual(result, {
417
435
  param: "value",
418
436
  });
419
437
  });
@@ -426,7 +444,7 @@ describe("getQueryParams", () => {
426
444
  };
427
445
  const result = getQueryParams(req);
428
446
 
429
- assert.deepStrictEqual(result, {
447
+ checkEqual(result, {
430
448
  param: "value",
431
449
  });
432
450
  });
@@ -450,84 +468,60 @@ describe("validateGitSource() tests", () => {
450
468
  });
451
469
 
452
470
  it("should reject ext:: and fd:: outright", () => {
453
- assert.deepStrictEqual(
471
+ checkEqual(
454
472
  validateAndRejectGitSource("ext::sh -c id").error,
455
473
  "Invalid Protocol",
456
474
  );
457
- assert.deepStrictEqual(
458
- validateAndRejectGitSource("fd::123").error,
459
- "Invalid Protocol",
460
- );
461
- assert.deepStrictEqual(
475
+ checkEqual(validateAndRejectGitSource("fd::123").error, "Invalid Protocol");
476
+ checkEqual(
462
477
  validateAndRejectGitSource("EXT::sh -c id").error,
463
478
  "Invalid Protocol",
464
479
  );
465
480
  });
466
481
 
467
482
  it("should allow standard local paths to bypass validation", () => {
468
- assert.deepStrictEqual(validateAndRejectGitSource("/tmp/local-path"), null);
469
- assert.deepStrictEqual(
470
- validateAndRejectGitSource("C:\\Users\\local"),
471
- null,
472
- );
483
+ checkEqual(validateAndRejectGitSource("/tmp/local-path"), null);
484
+ checkEqual(validateAndRejectGitSource("C:\\Users\\local"), null);
473
485
  });
474
486
 
475
487
  it("should handle ssh git@ format gracefully", () => {
476
- assert.deepStrictEqual(
477
- validateAndRejectGitSource("git@github.com:foo/bar.git"),
478
- null,
479
- );
488
+ checkEqual(validateAndRejectGitSource("git@github.com:foo/bar.git"), null);
480
489
  });
481
490
 
482
491
  it("should reject malformed git URLs", () => {
483
492
  // invalid URL format (can't parse via node's new URL object)
484
- assert.deepStrictEqual(
493
+ checkEqual(
485
494
  validateAndRejectGitSource("http://[:::1]/bad-ipv6").error,
486
495
  "Invalid URL Format",
487
496
  );
488
497
  });
489
498
 
490
499
  it("should enforce GIT_ALLOW_PROTOCOL default schemes", () => {
491
- assert.deepStrictEqual(
492
- validateAndRejectGitSource("https://github.com/repo"),
493
- null,
494
- );
495
- assert.deepStrictEqual(
496
- validateAndRejectGitSource("http://github.com/repo"),
497
- {
498
- status: 400,
499
- error: "Protocol Not Allowed",
500
- details: "The protocol 'http:' is not permitted by GIT_ALLOW_PROTOCOL.",
501
- },
502
- );
503
- assert.deepStrictEqual(
504
- validateAndRejectGitSource("git://github.com/repo"),
505
- null,
506
- );
507
- assert.deepStrictEqual(
508
- validateAndRejectGitSource("ssh://github.com/repo"),
509
- null,
510
- );
511
- assert.deepStrictEqual(
512
- validateAndRejectGitSource("git+ssh://github.com/repo"),
513
- null,
514
- );
500
+ checkEqual(validateAndRejectGitSource("https://github.com/repo"), null);
501
+ checkEqual(validateAndRejectGitSource("http://github.com/repo"), {
502
+ status: 400,
503
+ error: "Protocol Not Allowed",
504
+ details: "The protocol 'http:' is not permitted by GIT_ALLOW_PROTOCOL.",
505
+ });
506
+ checkEqual(validateAndRejectGitSource("git://github.com/repo"), null);
507
+ checkEqual(validateAndRejectGitSource("ssh://github.com/repo"), null);
508
+ checkEqual(validateAndRejectGitSource("git+ssh://github.com/repo"), null);
515
509
 
516
510
  // ftp is not allowed by default
517
511
  const res = validateAndRejectGitSource("ftp://github.com/repo");
518
- assert.deepStrictEqual(res.error, "Protocol Not Allowed");
519
- assert.deepStrictEqual(
512
+ checkEqual(res.error, "Protocol Not Allowed");
513
+ checkEqual(
520
514
  res.details,
521
515
  "The protocol 'ftp:' is not permitted by GIT_ALLOW_PROTOCOL.",
522
516
  );
523
517
  });
524
518
 
525
519
  it("should reject protocol smuggling techniques", () => {
526
- assert.deepStrictEqual(
520
+ checkEqual(
527
521
  validateAndRejectGitSource("git+ext://github.com/repo").error,
528
522
  "Protocol Not Allowed",
529
523
  );
530
- assert.deepStrictEqual(
524
+ checkEqual(
531
525
  validateAndRejectGitSource("http+ext://github.com/repo").error,
532
526
  "Protocol Not Allowed",
533
527
  );
@@ -535,30 +529,24 @@ describe("validateGitSource() tests", () => {
535
529
 
536
530
  it("should respect custom CDXGEN_SERVER_GIT_ALLOW_PROTOCOL configs", () => {
537
531
  process.env.CDXGEN_SERVER_GIT_ALLOW_PROTOCOL = "https:git";
538
- assert.deepStrictEqual(
539
- validateAndRejectGitSource("https://github.com/repo"),
540
- null,
541
- );
542
- assert.deepStrictEqual(
543
- validateAndRejectGitSource("git://github.com/repo"),
544
- null,
545
- );
532
+ checkEqual(validateAndRejectGitSource("https://github.com/repo"), null);
533
+ checkEqual(validateAndRejectGitSource("git://github.com/repo"), null);
546
534
 
547
535
  // http is no longer allowed
548
536
  const res = validateAndRejectGitSource("http://github.com/repo");
549
- assert.deepStrictEqual(res.error, "Protocol Not Allowed");
550
- assert.deepStrictEqual(
537
+ checkEqual(res.error, "Protocol Not Allowed");
538
+ checkEqual(
551
539
  res.details,
552
540
  "The protocol 'http:' is not permitted by GIT_ALLOW_PROTOCOL.",
553
541
  );
554
542
  });
555
543
 
556
544
  it("should reject remote helper syntax (::) inside valid schemes", () => {
557
- assert.deepStrictEqual(
545
+ checkEqual(
558
546
  validateAndRejectGitSource("https://github.com/ext::sh -c id").error,
559
547
  "Invalid URL Syntax",
560
548
  );
561
- assert.deepStrictEqual(
549
+ checkEqual(
562
550
  validateAndRejectGitSource("git://foo::bar/repo").error,
563
551
  "Invalid URL Format",
564
552
  );
@@ -566,44 +554,35 @@ describe("validateGitSource() tests", () => {
566
554
 
567
555
  it("should validate allowed hosts", () => {
568
556
  process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "github.com,gitlab.com";
569
- assert.deepStrictEqual(
570
- validateAndRejectGitSource("https://github.com/repo"),
571
- null,
572
- );
557
+ checkEqual(validateAndRejectGitSource("https://github.com/repo"), null);
573
558
 
574
559
  const res = validateAndRejectGitSource("https://evil.com/repo");
575
- assert.deepStrictEqual(res.error, "Host Not Allowed");
576
- assert.deepStrictEqual(res.status, 403);
560
+ checkEqual(res.error, "Host Not Allowed");
561
+ checkEqual(res.status, 403);
577
562
  });
578
563
  });
579
564
  it("should correctly normalize and validate various git@ (SCP-like) formats", () => {
580
- assert.deepStrictEqual(
565
+ checkEqual(
581
566
  validateAndRejectGitSource("git@gitlab.com:group/project.git"),
582
567
  null,
583
568
  );
584
- assert.deepStrictEqual(
569
+ checkEqual(
585
570
  validateAndRejectGitSource("git@bitbucket.org:workspace/repo:name.git"),
586
571
  null,
587
572
  );
588
- assert.deepStrictEqual(
589
- validateAndRejectGitSource("git@github.com/user/repo.git"),
590
- null,
591
- );
592
- assert.deepStrictEqual(
573
+ checkEqual(validateAndRejectGitSource("git@github.com/user/repo.git"), null);
574
+ checkEqual(
593
575
  validateAndRejectGitSource("ssh://git@github.com/user/repo.git"),
594
576
  null,
595
577
  );
596
578
  process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "github.com,bitbucket.org";
597
- assert.deepStrictEqual(
598
- validateAndRejectGitSource("git@github.com:user/repo.git"),
599
- null,
600
- );
601
- assert.deepStrictEqual(
579
+ checkEqual(validateAndRejectGitSource("git@github.com:user/repo.git"), null);
580
+ checkEqual(
602
581
  validateAndRejectGitSource("git@bitbucket.org:workspace/repo.git"),
603
582
  null,
604
583
  );
605
584
  const deniedRes = validateAndRejectGitSource("git@evil.com:foo/bar.git");
606
- assert.deepStrictEqual(deniedRes.status, 403);
607
- assert.deepStrictEqual(deniedRes.error, "Host Not Allowed");
585
+ checkEqual(deniedRes.status, 403);
586
+ checkEqual(deniedRes.error, "Host Not Allowed");
608
587
  delete process.env.CDXGEN_SERVER_ALLOWED_HOSTS;
609
588
  });