@cyclonedx/cdxgen 12.1.5 → 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.
- package/README.md +47 -39
- package/bin/cdxgen.js +175 -96
- package/bin/evinse.js +4 -4
- package/bin/repl.js +1 -1
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +327 -372
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +2 -14
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +423 -4
- package/lib/helpers/envcontext.js +18 -3
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +9 -0
- package/lib/helpers/utils.js +681 -406
- package/lib/helpers/utils.poku.js +55 -255
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +3 -9
- package/lib/parsers/npmrc.js +17 -13
- package/lib/parsers/npmrc.poku.js +41 -5
- package/lib/server/openapi.yaml +1 -1
- package/lib/server/server.js +40 -11
- package/lib/server/server.poku.js +123 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +197 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -8
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +10 -1
- package/types/lib/helpers/pythonutils.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +532 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +4 -1
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +21 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/lib/stages/pregen/env-audit.js +0 -34
- package/lib/stages/pregen/env-audit.poku.js +0 -290
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/lib/stages/pregen/env-audit.d.ts +0 -2
- package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
package/lib/managers/binary.js
CHANGED
|
@@ -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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 &&
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
539
|
+
if (safeExistsSync(join(src, "etc", "os-release"))) {
|
|
540
540
|
osReleaseFile = join(src, "etc", "os-release");
|
|
541
|
-
} else if (
|
|
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) {
|
package/lib/managers/docker.js
CHANGED
|
@@ -71,7 +71,14 @@ if (
|
|
|
71
71
|
isContainerd = true;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
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) {
|
package/lib/managers/oci.js
CHANGED
|
@@ -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 = [
|
package/lib/managers/piptree.js
CHANGED
|
@@ -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
|
|
@@ -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 (
|
|
251
|
+
if (safeExistsSync(pipTreeJson)) {
|
|
258
252
|
tree = JSON.parse(
|
|
259
253
|
readFileSync(pipTreeJson, {
|
|
260
254
|
encoding: "utf-8",
|
package/lib/parsers/npmrc.js
CHANGED
|
@@ -56,7 +56,7 @@ export function parseNpmrc(content) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Extract npm configuration values from environment variables.
|
|
59
|
+
* Extract npm/pnpm configuration values from environment variables.
|
|
60
60
|
* See https://docs.npmjs.com/cli/v11/using-npm/config
|
|
61
61
|
*
|
|
62
62
|
* npm uses the NPM_CONFIG_ prefix for env var config:
|
|
@@ -65,24 +65,28 @@ export function parseNpmrc(content) {
|
|
|
65
65
|
* - Simple keys are lowercased; scoped/URI keys preserve case
|
|
66
66
|
* - Boolean flags without values are treated as true
|
|
67
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
|
|
68
71
|
* @param {Object} env - Environment variables object (defaults to process.env)
|
|
69
72
|
* @returns {Object} Parsed npm config key-value pairs
|
|
70
73
|
*/
|
|
71
74
|
export function parseNpmrcFromEnv(env = process.env) {
|
|
72
75
|
const result = {};
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
for (const [
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;
|
|
84
89
|
}
|
|
85
|
-
result[configKey] = value === "" || value === undefined ? "true" : value;
|
|
86
90
|
}
|
|
87
91
|
return result;
|
|
88
92
|
}
|
|
@@ -415,16 +415,52 @@ const VALID_ENV_CASES = [
|
|
|
415
415
|
cache: "/tmp/cache",
|
|
416
416
|
},
|
|
417
417
|
},
|
|
418
|
-
{
|
|
419
|
-
name: "scoped registry auth in env",
|
|
420
|
-
env: { "npm_config_//registry.example.com/:_authToken": "secret123" },
|
|
421
|
-
expected: { "//registry.example.com/:_authToken": "secret123" },
|
|
422
|
-
},
|
|
423
418
|
{
|
|
424
419
|
name: "unicode values preserved",
|
|
425
420
|
env: { npm_config_description: "测试🔐" },
|
|
426
421
|
expected: { description: "测试🔐" },
|
|
427
422
|
},
|
|
423
|
+
{
|
|
424
|
+
name: "basic pnpm_config_ prefix",
|
|
425
|
+
env: { pnpm_config_registry: "https://pnpm-registry.example.com" },
|
|
426
|
+
expected: { registry: "https://pnpm-registry.example.com" },
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "case-insensitive PNPM_CONFIG_ prefix",
|
|
430
|
+
env: { PNPM_CONFIG_PROXY: "http://proxy.local" },
|
|
431
|
+
expected: { proxy: "http://proxy.local" },
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "pnpm_config_ simple key lowercased",
|
|
435
|
+
env: { PNPM_CONFIG_STORE_DIR: "/custom/store" },
|
|
436
|
+
expected: { store_dir: "/custom/store" },
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "pnpm_config_ boolean flag with empty value → true",
|
|
440
|
+
env: { pnpm_config_shamefully_hoist: "" },
|
|
441
|
+
expected: { shamefully_hoist: "true" },
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "pnpm_config_ overrides npm_config_ for same key",
|
|
445
|
+
env: {
|
|
446
|
+
npm_config_registry: "https://npm-registry.com",
|
|
447
|
+
pnpm_config_registry: "https://pnpm-registry.com",
|
|
448
|
+
},
|
|
449
|
+
expected: { registry: "https://pnpm-registry.com" },
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: "pnpm_config_ and npm_config_ for different keys are both included",
|
|
453
|
+
env: {
|
|
454
|
+
npm_config_cache: "/npm-cache",
|
|
455
|
+
pnpm_config_store_dir: "/pnpm-store",
|
|
456
|
+
},
|
|
457
|
+
expected: { cache: "/npm-cache", store_dir: "/pnpm-store" },
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "empty config key after pnpm_config_ prefix ignored",
|
|
461
|
+
env: { pnpm_config_: "value" },
|
|
462
|
+
expected: {},
|
|
463
|
+
},
|
|
428
464
|
];
|
|
429
465
|
|
|
430
466
|
const EDGE_ENV_CASES = [
|
package/lib/server/openapi.yaml
CHANGED
package/lib/server/server.js
CHANGED
|
@@ -372,6 +372,17 @@ export function parseValue(raw) {
|
|
|
372
372
|
throw new TypeError(`Invalid value type: ${t}.`);
|
|
373
373
|
}
|
|
374
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
|
+
*/
|
|
375
386
|
export function parseQueryString(q, body = {}, options = {}) {
|
|
376
387
|
// Priority is query params followed by body
|
|
377
388
|
for (const param of ALLOWED_PARAMS) {
|
|
@@ -391,6 +402,14 @@ export function parseQueryString(q, body = {}, options = {}) {
|
|
|
391
402
|
return options;
|
|
392
403
|
}
|
|
393
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
|
+
*/
|
|
394
413
|
export function getQueryParams(req) {
|
|
395
414
|
try {
|
|
396
415
|
if (!req?.url) {
|
|
@@ -402,7 +421,7 @@ export function getQueryParams(req) {
|
|
|
402
421
|
const baseUrl = `${protocol}://${host}`;
|
|
403
422
|
|
|
404
423
|
const fullUrl = new URL(req.url, baseUrl);
|
|
405
|
-
const params =
|
|
424
|
+
const params = Object.create(null);
|
|
406
425
|
|
|
407
426
|
// Convert multiple values to an array
|
|
408
427
|
for (const [key, value] of fullUrl.searchParams) {
|
|
@@ -449,21 +468,25 @@ const configureServer = (cdxgenServer) => {
|
|
|
449
468
|
const ALL_INTERFACES = new Set(["0.0.0.0", "::", "::/128", "::/0"]);
|
|
450
469
|
|
|
451
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
|
+
}
|
|
452
477
|
console.log(`cdxgen server version ${CDXGEN_VERSION}`);
|
|
453
|
-
|
|
454
|
-
console.log(
|
|
455
|
-
"Listening on",
|
|
456
|
-
options.serverHost,
|
|
457
|
-
options.serverPort,
|
|
458
|
-
"without authentication!",
|
|
459
|
-
);
|
|
460
478
|
if (ALL_INTERFACES.has(options.serverHost)) {
|
|
461
479
|
console.log("Exposing cdxgen server on all IP address is a security risk!");
|
|
462
480
|
if (isSecureMode) {
|
|
463
481
|
process.exit(1);
|
|
464
482
|
}
|
|
465
483
|
}
|
|
466
|
-
|
|
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) {
|
|
467
490
|
console.log(
|
|
468
491
|
"Running cdxgen server with a privileged port is a security risk!",
|
|
469
492
|
);
|
|
@@ -503,9 +526,15 @@ const start = (options) => {
|
|
|
503
526
|
process.exit(1);
|
|
504
527
|
}
|
|
505
528
|
}
|
|
529
|
+
console.log(
|
|
530
|
+
"Listening on",
|
|
531
|
+
options.serverHost,
|
|
532
|
+
serverPort,
|
|
533
|
+
"without authentication!",
|
|
534
|
+
);
|
|
506
535
|
const cdxgenServer = http
|
|
507
536
|
.createServer(app)
|
|
508
|
-
.listen(
|
|
537
|
+
.listen(serverPort, options.serverHost);
|
|
509
538
|
configureServer(cdxgenServer);
|
|
510
539
|
|
|
511
540
|
app.use("/health", (_req, res) => {
|
|
@@ -525,7 +554,7 @@ const start = (options) => {
|
|
|
525
554
|
}
|
|
526
555
|
const q = getQueryParams(req);
|
|
527
556
|
let cleanup = false;
|
|
528
|
-
let reqOptions =
|
|
557
|
+
let reqOptions = Object.create(null);
|
|
529
558
|
try {
|
|
530
559
|
reqOptions = parseQueryString(
|
|
531
560
|
q,
|