@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
@@ -1,5 +1,4 @@
1
1
  import {
2
- existsSync,
3
2
  lstatSync,
4
3
  mkdtempSync,
5
4
  readFileSync,
@@ -31,6 +30,7 @@ import {
31
30
  isSpdxLicenseExpression,
32
31
  multiChecksumFile,
33
32
  retrieveCdxgenPluginVersion,
33
+ safeExistsSync,
34
34
  safeMkdirSync,
35
35
  safeSpawnSync,
36
36
  } from "../helpers/utils.js";
@@ -82,8 +82,8 @@ let extraNMBinPath;
82
82
  // Is there a non-empty local plugins directory
83
83
  if (
84
84
  !CDXGEN_PLUGINS_DIR &&
85
- existsSync(join(dirName, "plugins")) &&
86
- existsSync(join(dirName, "plugins", "trivy"))
85
+ safeExistsSync(join(dirName, "plugins")) &&
86
+ safeExistsSync(join(dirName, "plugins", "trivy"))
87
87
  ) {
88
88
  CDXGEN_PLUGINS_DIR = join(dirName, "plugins");
89
89
  }
@@ -91,7 +91,7 @@ if (
91
91
  // Is there a non-empty local node_modules directory
92
92
  if (
93
93
  !CDXGEN_PLUGINS_DIR &&
94
- existsSync(
94
+ safeExistsSync(
95
95
  join(
96
96
  dirName,
97
97
  "node_modules",
@@ -100,7 +100,7 @@ if (
100
100
  "plugins",
101
101
  ),
102
102
  ) &&
103
- existsSync(
103
+ safeExistsSync(
104
104
  join(
105
105
  dirName,
106
106
  "node_modules",
@@ -118,7 +118,7 @@ if (
118
118
  `cdxgen-plugins-bin${pluginsBinSuffix}`,
119
119
  "plugins",
120
120
  );
121
- if (existsSync(join(dirName, "node_modules", ".bin"))) {
121
+ if (safeExistsSync(join(dirName, "node_modules", ".bin"))) {
122
122
  extraNMBinPath = join(dirName, "node_modules", ".bin");
123
123
  }
124
124
  }
@@ -168,7 +168,7 @@ if (!CDXGEN_PLUGINS_DIR) {
168
168
  `cdxgen-plugins-bin${pluginsBinSuffix}`,
169
169
  "plugins",
170
170
  );
171
- if (existsSync(join(tmpA[0], "node_modules", ".bin"))) {
171
+ if (safeExistsSync(join(tmpA[0], "node_modules", ".bin"))) {
172
172
  extraNMBinPath = join(tmpA[0], "node_modules", ".bin");
173
173
  }
174
174
  } else if (dirName.includes(join(".pnpm", "@cyclonedx+cdxgen"))) {
@@ -183,7 +183,7 @@ if (!CDXGEN_PLUGINS_DIR) {
183
183
  `cdxgen-plugins-bin${pluginsBinSuffix}`,
184
184
  "plugins",
185
185
  );
186
- if (existsSync(join(tmpA[0], ".bin"))) {
186
+ if (safeExistsSync(join(tmpA[0], ".bin"))) {
187
187
  extraNMBinPath = join(tmpA[0], ".bin");
188
188
  }
189
189
  } else if (dirName.includes(join("caxa", "applications"))) {
@@ -201,12 +201,12 @@ if (!CDXGEN_PLUGINS_DIR) {
201
201
  extraNMBinPath = join(dirName, "node_modules", ".bin");
202
202
  }
203
203
  // Set the plugins directory
204
- if (globalPlugins && existsSync(globalPlugins)) {
204
+ if (globalPlugins && safeExistsSync(globalPlugins)) {
205
205
  CDXGEN_PLUGINS_DIR = globalPlugins;
206
206
  if (DEBUG_MODE) {
207
207
  console.log("Found global plugins", CDXGEN_PLUGINS_DIR);
208
208
  }
209
- } else if (altGlobalPlugins && existsSync(altGlobalPlugins)) {
209
+ } else if (altGlobalPlugins && safeExistsSync(altGlobalPlugins)) {
210
210
  CDXGEN_PLUGINS_DIR = altGlobalPlugins;
211
211
  // To help detect bin commands such as atom, astgen, etc, we need to set this to the PATH variable.
212
212
  if (DEBUG_MODE) {
@@ -227,7 +227,7 @@ if (!CDXGEN_PLUGINS_DIR) {
227
227
  CDXGEN_PLUGINS_DIR = "";
228
228
  }
229
229
  let TRIVY_BIN = process.env.TRIVY_CMD;
230
- if (existsSync(join(CDXGEN_PLUGINS_DIR, "trivy"))) {
230
+ if (safeExistsSync(join(CDXGEN_PLUGINS_DIR, "trivy"))) {
231
231
  TRIVY_BIN = join(
232
232
  CDXGEN_PLUGINS_DIR,
233
233
  "trivy",
@@ -235,7 +235,7 @@ if (existsSync(join(CDXGEN_PLUGINS_DIR, "trivy"))) {
235
235
  );
236
236
  }
237
237
  let CARGO_AUDITABLE_BIN = process.env.CARGO_AUDITABLE_CMD;
238
- if (existsSync(join(CDXGEN_PLUGINS_DIR, "cargo-auditable"))) {
238
+ if (safeExistsSync(join(CDXGEN_PLUGINS_DIR, "cargo-auditable"))) {
239
239
  CARGO_AUDITABLE_BIN = join(
240
240
  CDXGEN_PLUGINS_DIR,
241
241
  "cargo-auditable",
@@ -243,7 +243,7 @@ if (existsSync(join(CDXGEN_PLUGINS_DIR, "cargo-auditable"))) {
243
243
  );
244
244
  }
245
245
  let OSQUERY_BIN = process.env.OSQUERY_CMD;
246
- if (existsSync(join(CDXGEN_PLUGINS_DIR, "osquery"))) {
246
+ if (safeExistsSync(join(CDXGEN_PLUGINS_DIR, "osquery"))) {
247
247
  OSQUERY_BIN = join(
248
248
  CDXGEN_PLUGINS_DIR,
249
249
  "osquery",
@@ -255,7 +255,7 @@ if (existsSync(join(CDXGEN_PLUGINS_DIR, "osquery"))) {
255
255
  }
256
256
  }
257
257
  let DOSAI_BIN = process.env.DOSAI_CMD;
258
- if (existsSync(join(CDXGEN_PLUGINS_DIR, "dosai"))) {
258
+ if (safeExistsSync(join(CDXGEN_PLUGINS_DIR, "dosai"))) {
259
259
  DOSAI_BIN = join(
260
260
  CDXGEN_PLUGINS_DIR,
261
261
  "dosai",
@@ -268,7 +268,7 @@ const BLINT_BIN = process.env.BLINT_CMD || "blint";
268
268
 
269
269
  // sourcekitten
270
270
  let SOURCEKITTEN_BIN = process.env.SOURCEKITTEN_CMD;
271
- if (existsSync(join(CDXGEN_PLUGINS_DIR, "sourcekitten"))) {
271
+ if (safeExistsSync(join(CDXGEN_PLUGINS_DIR, "sourcekitten"))) {
272
272
  SOURCEKITTEN_BIN = join(CDXGEN_PLUGINS_DIR, "sourcekitten", "sourcekitten");
273
273
  }
274
274
 
@@ -476,7 +476,7 @@ export async function getOSPackages(src, imageConfig) {
476
476
  } catch (_err) {
477
477
  // ignore errors
478
478
  }
479
- if (existsSync(src)) {
479
+ if (safeExistsSync(src)) {
480
480
  imageType = "rootfs";
481
481
  }
482
482
  const tempDir = mkdtempSync(join(getTmpDir(), "trivy-cdxgen-"));
@@ -513,7 +513,7 @@ export async function getOSPackages(src, imageConfig) {
513
513
  console.error(result.stdout, result.stderr);
514
514
  }
515
515
  }
516
- if (existsSync(bomJsonFile)) {
516
+ if (safeExistsSync(bomJsonFile)) {
517
517
  let tmpBom = {};
518
518
  try {
519
519
  tmpBom = JSON.parse(
@@ -536,9 +536,9 @@ export async function getOSPackages(src, imageConfig) {
536
536
  const osReleaseData = {};
537
537
  let osReleaseFile;
538
538
  // Let's try to read the os-release file from various locations
539
- if (existsSync(join(src, "etc", "os-release"))) {
539
+ if (safeExistsSync(join(src, "etc", "os-release"))) {
540
540
  osReleaseFile = join(src, "etc", "os-release");
541
- } else if (existsSync(join(src, "usr", "lib", "os-release"))) {
541
+ } else if (safeExistsSync(join(src, "usr", "lib", "os-release"))) {
542
542
  osReleaseFile = join(src, "usr", "lib", "os-release");
543
543
  }
544
544
  if (osReleaseFile) {
@@ -71,7 +71,14 @@ if (
71
71
  isContainerd = true;
72
72
  }
73
73
 
74
- // Taken from https://github.com/isaacs/node-tar/blob/main/src/strip-absolute-path.ts
74
+ /**
75
+ * Strip absolute path prefixes from a path string, handling both Unix and
76
+ * Windows paths (including UNC and extended-length paths such as //?/C:/).
77
+ * Taken from https://github.com/isaacs/node-tar/blob/main/src/strip-absolute-path.ts
78
+ *
79
+ * @param {string} path The path to strip
80
+ * @returns {string} The path with its absolute root removed
81
+ */
75
82
  export const stripAbsolutePath = (path) => {
76
83
  // This appears to be a most frequent case, so let's return quickly.
77
84
  if (path === "/") {
@@ -165,6 +172,18 @@ export function detectRancherDesktop() {
165
172
  // Cache the registry auth keys
166
173
  const registry_auth_keys = {};
167
174
  const REQUEST_TIMEOUT_SECS = 60000;
175
+ /**
176
+ * Build a `got` options object for Docker / registry API requests. Resolves
177
+ * authentication headers by consulting (in order) the DOCKER_AUTH_CONFIG
178
+ * environment variable, DOCKER_USER/DOCKER_PASSWORD/DOCKER_EMAIL environment
179
+ * variables, hardcoded tokens in ~/.docker/config.json, credential helpers
180
+ * listed in credHelpers/credsStore, and finally TLS certificate files pointed
181
+ * to by DOCKER_CERT_PATH.
182
+ *
183
+ * @param {string} [forRegistry] Registry hostname (e.g. "registry-1.docker.io").
184
+ * Defaults to DOCKER_SERVER_ADDRESS env var or "docker.io".
185
+ * @returns {Object} Options object suitable for passing to `got`
186
+ */
168
187
  const getDefaultOptions = (forRegistry) => {
169
188
  let authTokenSet = false;
170
189
  if (!forRegistry) {
@@ -358,6 +377,20 @@ const getDefaultOptions = (forRegistry) => {
358
377
  return opts;
359
378
  };
360
379
 
380
+ /**
381
+ * Establish (or reuse) a `got` client connected to the local Docker or Podman
382
+ * daemon. Tries multiple socket / URL candidates in order: the default Docker
383
+ * socket, the rootless Docker socket, the Windows TCP endpoint, the rootless
384
+ * Podman socket, and the root Podman socket. Sets the module-level flags
385
+ * `isPodman`, `isPodmanRootless`, `isDockerRootless`, and `isWinLocalTLS` as a
386
+ * side-effect. Returns `undefined` when containerd / nerdctl is in use or no
387
+ * daemon could be reached.
388
+ *
389
+ * @param {Object} options Additional `got` options to merge into the connection
390
+ * @param {string} [forRegistry] Registry hostname forwarded to `getDefaultOptions`
391
+ * @returns {Promise<import("got").Got|undefined>} A `got` instance bound to the
392
+ * daemon base URL, or `undefined`
393
+ */
361
394
  export const getConnection = async (options, forRegistry) => {
362
395
  if (isContainerd || isNerdctl) {
363
396
  return undefined;
@@ -461,6 +494,17 @@ export const getConnection = async (options, forRegistry) => {
461
494
  return dockerConn;
462
495
  };
463
496
 
497
+ /**
498
+ * Send a single HTTP request to the Docker / Podman daemon via the `got`
499
+ * client returned by {@link getConnection}. GET requests are parsed as JSON;
500
+ * all other methods receive a Buffer response body.
501
+ *
502
+ * @param {string} path API path relative to the daemon base URL (e.g. "images/ubuntu:latest/json")
503
+ * @param {string} method HTTP method (e.g. "GET", "POST", "DELETE")
504
+ * @param {string} [forRegistry] Registry hostname forwarded to `getDefaultOptions` for auth headers
505
+ * @returns {Promise<Object|Buffer|undefined>} Parsed JSON object for GET
506
+ * requests, raw Buffer for other methods, or `undefined` if no client is available
507
+ */
464
508
  export const makeRequest = async (path, method, forRegistry) => {
465
509
  const client = await getConnection({}, forRegistry);
466
510
  if (!client) {
@@ -815,6 +859,14 @@ function tarFilter(path, entry) {
815
859
  );
816
860
  }
817
861
 
862
+ /**
863
+ * Suppress low-signal tar warnings (TAR_ENTRY_INFO, TAR_LONGLINK) that are
864
+ * expected when extracting container image layers. All other warning codes are
865
+ * logged when DEBUG_MODE is enabled.
866
+ *
867
+ * @param {string} code Tar warning code (e.g. "TAR_ENTRY_INFO")
868
+ * @param {string} message Human-readable warning message
869
+ */
818
870
  function handleTarWarning(code, message) {
819
871
  if (code === "TAR_ENTRY_INFO" || code === "TAR_LONGLINK") {
820
872
  return;
@@ -885,6 +937,18 @@ const EXTRACT_EXCLUDE_TYPES = new Set([
885
937
  "Link",
886
938
  ]);
887
939
 
940
+ /**
941
+ * Extract a container image tar archive into a destination directory.
942
+ * Applies path sanitisation, ownership/permission preservation settings, and
943
+ * an entry filter to skip problematic files and device nodes. Handles common
944
+ * tar errors gracefully, logging only unexpected ones.
945
+ *
946
+ * @param {string} fullImageName Path to the source tar archive
947
+ * @param {string} dir Destination directory to extract into
948
+ * @param {Object} options CLI options (uses `options.failOnError`)
949
+ * @returns {Promise<boolean>} `true` on success, `false` when the archive is
950
+ * empty or a non-fatal error was encountered
951
+ */
888
952
  export const extractTar = async (fullImageName, dir, options) => {
889
953
  try {
890
954
  await stream.pipeline(
@@ -1016,6 +1080,21 @@ export const exportArchive = async (fullImageName, options = {}) => {
1016
1080
  return undefined;
1017
1081
  };
1018
1082
 
1083
+ /**
1084
+ * Parse a Docker/containerd manifest file and extract all image layers into a
1085
+ * single merged directory. Resolves the last layer's config to determine the
1086
+ * container's working directory, and builds the package path list for
1087
+ * subsequent analysis.
1088
+ *
1089
+ * @param {string} manifestFile Path to the manifest.json (or index.json) file
1090
+ * @param {Object} localData Local image inspect data (e.g. from `docker inspect`)
1091
+ * @param {string} tempDir Temporary directory that holds the unpacked image
1092
+ * @param {string} allLayersExplodedDir Directory where all layers are merged
1093
+ * @param {Object} options CLI options (uses `options.failOnError`)
1094
+ * @returns {Promise<Object>} Export data object containing `manifest`,
1095
+ * `allLayersDir`, `allLayersExplodedDir`, `lastLayerConfig`,
1096
+ * `lastWorkingDir`, `binPaths`, and `pkgPathList`
1097
+ */
1019
1098
  export const extractFromManifest = async (
1020
1099
  manifestFile,
1021
1100
  localData,
@@ -1414,10 +1493,29 @@ export const getPkgPathList = (exportData, lastWorkingDir) => {
1414
1493
  return pathList;
1415
1494
  };
1416
1495
 
1496
+ /**
1497
+ * Remove a container image from the local Docker / Podman daemon.
1498
+ *
1499
+ * @param {string} fullImageName Full image name including tag or digest (e.g. "ubuntu:22.04")
1500
+ * @param {boolean} [force=false] When `true`, force-remove the image even if it is in use
1501
+ * @returns {Promise<Buffer|undefined>} Raw response buffer from the daemon, or
1502
+ * `undefined` if no daemon connection is available
1503
+ */
1417
1504
  export const removeImage = async (fullImageName, force = false) => {
1418
1505
  return await makeRequest(`images/${fullImageName}?force=${force}`, "DELETE");
1419
1506
  };
1420
1507
 
1508
+ /**
1509
+ * Retrieve a base64url-encoded authentication token for a registry server by
1510
+ * invoking the `docker-credential-<exeSuffix>` credential helper binary.
1511
+ * Results are cached in `registry_auth_keys` to avoid redundant subprocess
1512
+ * calls.
1513
+ *
1514
+ * @param {string} exeSuffix Credential helper name suffix (e.g. "osxkeychain", "wincred", "pass")
1515
+ * @param {string} serverAddress Registry server address (e.g. "https://index.docker.io/v1/")
1516
+ * @returns {string|undefined} Base64url-encoded JSON auth token, or `undefined`
1517
+ * if the helper is unavailable or returns an error
1518
+ */
1421
1519
  export const getCredsFromHelper = (exeSuffix, serverAddress) => {
1422
1520
  if (registry_auth_keys[serverAddress]) {
1423
1521
  return registry_auth_keys[serverAddress];
@@ -1460,6 +1558,15 @@ export const getCredsFromHelper = (exeSuffix, serverAddress) => {
1460
1558
  return undefined;
1461
1559
  };
1462
1560
 
1561
+ /**
1562
+ * Append skipped source-file entries to the `SrcFile` properties of matching
1563
+ * components. A component matches when its `oci:SrcImage` property value
1564
+ * equals the skipped image's `image` field and the source file path is not
1565
+ * already listed.
1566
+ *
1567
+ * @param {Array<{image: string, src: string}>} skippedImageSrcs List of skipped image/source pairs
1568
+ * @param {Array<Object>} components CycloneDX component objects to update in place
1569
+ */
1463
1570
  export const addSkippedSrcFiles = (skippedImageSrcs, components) => {
1464
1571
  for (const skippedImage of skippedImageSrcs) {
1465
1572
  for (const co of components) {
@@ -9,6 +9,16 @@ import {
9
9
  safeSpawnSync,
10
10
  } from "../helpers/utils.js";
11
11
 
12
+ /**
13
+ * Retrieves a CycloneDX BOM attached to an OCI image using the `oras` CLI tool.
14
+ * Discovers SBOM attachments via `oras discover`, pulls the first matching
15
+ * artifact, and returns the parsed BOM JSON. Retries automatically with a
16
+ * platform-specific manifest when the initial platform-agnostic discovery fails.
17
+ *
18
+ * @param {string} image OCI image reference (e.g. `"registry.example.com/org/app:tag"`)
19
+ * @param {string} [platform] OCI platform string (e.g. `"linux/amd64"`); detected automatically when omitted
20
+ * @returns {Object|undefined} Parsed CycloneDX BOM JSON object, or `undefined` if not found
21
+ */
12
22
  export function getBomWithOras(image, platform = undefined) {
13
23
  const platformArch = arch() === "arm64" ? "arm64" : "amd64";
14
24
  let parameters = [
@@ -4,16 +4,10 @@
4
4
  *
5
5
  * We use the internal pip api to construct the dependency tree for modern python + pip environments
6
6
  */
7
- import {
8
- existsSync,
9
- mkdtempSync,
10
- readFileSync,
11
- rmSync,
12
- writeFileSync,
13
- } from "node:fs";
7
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
14
8
  import { delimiter, join } from "node:path";
15
9
 
16
- import { getTmpDir, safeSpawnSync } from "../helpers/utils.js";
10
+ import { getTmpDir, safeExistsSync, safeSpawnSync } from "../helpers/utils.js";
17
11
 
18
12
  const PIP_TREE_PLUGIN_CONTENT = `
19
13
  import importlib.metadata as importlib_metadata
@@ -125,7 +119,7 @@ def get_installed_with_extras():
125
119
  name_version_cache[name] = version
126
120
  name_dist_cache[name] = dist
127
121
  for dist in importlib_metadata.distributions():
128
- name = dist.metadata['Name']
122
+ name = dist.metadata['Name'] or ""
129
123
  version = dist.version or ""
130
124
  # extras this package defines:
131
125
  extras = dist.metadata.get_all('Provides-Extra') or []
@@ -254,7 +248,7 @@ export const getTreeWithPlugin = (env, python_cmd, basePath) => {
254
248
  console.log(result.stdout, result.stderr);
255
249
  }
256
250
  }
257
- if (existsSync(pipTreeJson)) {
251
+ if (safeExistsSync(pipTreeJson)) {
258
252
  tree = JSON.parse(
259
253
  readFileSync(pipTreeJson, {
260
254
  encoding: "utf-8",
@@ -0,0 +1,92 @@
1
+ export const DEFAULT_NPMRC_BLOCKLIST = new Set([
2
+ "git",
3
+ "script-shell",
4
+ "shell",
5
+ "call",
6
+ "browser",
7
+ "replace-registry-host",
8
+ "node-gyp",
9
+ "node-options",
10
+ "bin-links",
11
+ "install-links",
12
+ "location",
13
+ "userconfig",
14
+ "globalconfig",
15
+ "foreground-scripts",
16
+ "rebuild-bundle",
17
+ ]);
18
+
19
+ /**
20
+ * Parse .npmrc content into a plain key-value object.
21
+ *
22
+ * @param {string} content - Raw .npmrc file content
23
+ * @returns {Object} Parsed key-value pairs
24
+ */
25
+ export function parseNpmrc(content) {
26
+ const result = {};
27
+ const lines = content.split(/\r\n|\r|\n/);
28
+ for (const rawLine of lines) {
29
+ const line = rawLine.trim();
30
+ if (!line || line.startsWith("#") || line.startsWith(";")) {
31
+ continue;
32
+ }
33
+ const eqIndex = line.indexOf("=");
34
+ if (eqIndex === -1) continue;
35
+ let key = line.substring(0, eqIndex).trim();
36
+ let value = line.substring(eqIndex + 1).trim();
37
+ if (!key) continue;
38
+ const isArray = key.endsWith("[]");
39
+ if (isArray) {
40
+ key = key.slice(0, -2);
41
+ }
42
+ if (
43
+ (value.startsWith('"') && value.endsWith('"')) ||
44
+ (value.startsWith("'") && value.endsWith("'"))
45
+ ) {
46
+ value = value.slice(1, -1);
47
+ }
48
+ if (isArray) {
49
+ if (!result[key]) result[key] = [];
50
+ result[key].push(value);
51
+ } else {
52
+ result[key] = value;
53
+ }
54
+ }
55
+ return result;
56
+ }
57
+
58
+ /**
59
+ * Extract npm/pnpm configuration values from environment variables.
60
+ * See https://docs.npmjs.com/cli/v11/using-npm/config
61
+ *
62
+ * npm uses the NPM_CONFIG_ prefix for env var config:
63
+ * - Dashes become underscores: --allow-same-version → npm_config_allow_same_version
64
+ * - Case-insensitive prefix matching: NPM_CONFIG_FOO = npm_config_foo
65
+ * - Simple keys are lowercased; scoped/URI keys preserve case
66
+ * - Boolean flags without values are treated as true
67
+ *
68
+ * pnpm v11+ uses the PNPM_CONFIG_ prefix instead of NPM_CONFIG_ for pnpm-specific settings.
69
+ * Both prefixes are supported; pnpm_config_* takes precedence over npm_config_* for the same key.
70
+ * See https://pnpm.io/next/npmrc
71
+ * @param {Object} env - Environment variables object (defaults to process.env)
72
+ * @returns {Object} Parsed npm config key-value pairs
73
+ */
74
+ export function parseNpmrcFromEnv(env = process.env) {
75
+ const result = {};
76
+ const NPM_PREFIX = "npm_config_";
77
+ const PNPM_PREFIX = "pnpm_config_";
78
+ for (const prefix of [NPM_PREFIX, PNPM_PREFIX]) {
79
+ for (const [fullKey, value] of Object.entries(env)) {
80
+ if (!fullKey.toLowerCase().startsWith(prefix)) {
81
+ continue;
82
+ }
83
+ let configKey = fullKey.slice(prefix.length);
84
+ if (!configKey) continue;
85
+ if (!configKey.startsWith("//") && !configKey.startsWith("@")) {
86
+ configKey = configKey.toLowerCase();
87
+ }
88
+ result[configKey] = value === "" || value === undefined ? "true" : value;
89
+ }
90
+ }
91
+ return result;
92
+ }