@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.
- package/README.md +47 -39
- package/bin/cdxgen.js +181 -90
- package/bin/evinse.js +4 -4
- package/bin/repl.js +3 -3
- 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 +484 -440
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +5 -18
- package/lib/evinser/swiftsem.js +1 -1
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/caxa.js +1 -1
- 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 +429 -14
- package/lib/helpers/envcontext.js +23 -8
- 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 +305 -0
- package/lib/helpers/pythonutils.poku.js +469 -0
- package/lib/helpers/utils.js +970 -528
- package/lib/helpers/utils.poku.js +139 -256
- 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 +4 -10
- package/lib/parsers/npmrc.js +92 -0
- package/lib/parsers/npmrc.poku.js +528 -0
- package/lib/server/openapi.yaml +1 -10
- package/lib/server/server.js +58 -16
- 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/third-party/arborist/lib/deepest-nesting-target.js +1 -1
- package/lib/third-party/arborist/lib/node.js +3 -3
- package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
- package/lib/third-party/arborist/lib/tree-check.js +1 -1
- 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 +18 -0
- package/types/lib/helpers/pythonutils.d.ts.map +1 -0
- 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 +26 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- 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/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/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/helpers/utils.js
CHANGED
|
@@ -48,15 +48,16 @@ import {
|
|
|
48
48
|
satisfies,
|
|
49
49
|
valid,
|
|
50
50
|
} from "semver";
|
|
51
|
-
import { v4 as uuidv4 } from "uuid";
|
|
52
51
|
import { xml2js } from "xml-js";
|
|
53
|
-
import { parse as _load } from "yaml";
|
|
52
|
+
import { parse as _load, parseAllDocuments } from "yaml";
|
|
54
53
|
|
|
55
54
|
import { getTreeWithPlugin } from "../managers/piptree.js";
|
|
56
55
|
import { IriValidationStrategy, validateIri } from "../parsers/iri.js";
|
|
57
56
|
import Arborist from "../third-party/arborist/lib/index.js";
|
|
57
|
+
import { parseWorkflowFile } from "./ciParsers/githubActions.js";
|
|
58
58
|
import { extractPackageInfoFromHintPath } from "./dotnetutils.js";
|
|
59
59
|
import { thoughtLog, traceLog } from "./logger.js";
|
|
60
|
+
import { get_python_command_from_env, getVenvMetadata } from "./pythonutils.js";
|
|
60
61
|
|
|
61
62
|
let url = import.meta?.url;
|
|
62
63
|
if (url && !url.startsWith("file://")) {
|
|
@@ -78,12 +79,6 @@ export const isDeno = globalThis.Deno?.version?.deno !== undefined;
|
|
|
78
79
|
|
|
79
80
|
export const isWin = platform() === "win32";
|
|
80
81
|
export const isMac = platform() === "darwin";
|
|
81
|
-
export let ATOM_DB = join(homedir(), ".local", "share", ".atomdb");
|
|
82
|
-
if (isWin) {
|
|
83
|
-
ATOM_DB = join(homedir(), "AppData", "Local", ".atomdb");
|
|
84
|
-
} else if (isMac) {
|
|
85
|
-
ATOM_DB = join(homedir(), "Library", "Application Support", ".atomdb");
|
|
86
|
-
}
|
|
87
82
|
|
|
88
83
|
/**
|
|
89
84
|
* Safely check if a file path exists without crashing due to a lack of permissions
|
|
@@ -123,14 +118,68 @@ export function safeMkdirSync(filePath, options) {
|
|
|
123
118
|
}
|
|
124
119
|
|
|
125
120
|
export const commandsExecuted = new Set();
|
|
121
|
+
const ALLOW_COMMANDS = (process.env.CDXGEN_ALLOWED_COMMANDS || "").split(",");
|
|
126
122
|
function isAllowedCommand(command) {
|
|
127
123
|
if (!process.env.CDXGEN_ALLOWED_COMMANDS) {
|
|
128
124
|
return true;
|
|
129
125
|
}
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
return ALLOW_COMMANDS.includes(command.trim());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const ALLOWED_WRAPPERS = new Set(["gradlew", "mvnw"]);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Check for Windows CWD executable hijack when shell: true is used.
|
|
133
|
+
* cmd.exe searches CWD before PATH, allowing local files to shadow system commands.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} command The executable to spawn
|
|
136
|
+
* @param {Object} options Options forwarded to spawnSync (e.g. cwd, env, shell)
|
|
137
|
+
*
|
|
138
|
+
* @returns {boolean} true if there is a hijack risk. false otherwise.
|
|
139
|
+
*/
|
|
140
|
+
function isWindowsShellHijackRisk(command, options) {
|
|
141
|
+
const cwd = options?.cwd;
|
|
142
|
+
const usesShell = options?.shell === true;
|
|
143
|
+
if (!isWin || !usesShell || !cwd || !command) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
if (/[\/\\]/.test(command)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const cmdBase = command.toLowerCase();
|
|
150
|
+
if (ALLOWED_WRAPPERS.has(cmdBase)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const pathExt = (
|
|
154
|
+
process.env.PATHEXT ||
|
|
155
|
+
".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC"
|
|
156
|
+
)
|
|
157
|
+
.split(";")
|
|
158
|
+
.filter(Boolean);
|
|
159
|
+
const candidates = [
|
|
160
|
+
cmdBase,
|
|
161
|
+
...pathExt.map((ext) => cmdBase + ext.toLowerCase()),
|
|
162
|
+
];
|
|
163
|
+
const absCwd = resolve(cwd);
|
|
164
|
+
for (const candidate of candidates) {
|
|
165
|
+
const candidatePath = path.join(absCwd, candidate);
|
|
166
|
+
if (existsSync(candidatePath)) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
132
171
|
}
|
|
133
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Safe wrapper around spawnSync that enforces permission checks, injects default
|
|
175
|
+
* options (maxBuffer, encoding, timeout), warns about unsafe Python and pip/uv
|
|
176
|
+
* invocations, and records every executed command in the commandsExecuted set.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} command The executable to spawn
|
|
179
|
+
* @param {string[]} args Arguments to pass to the command
|
|
180
|
+
* @param {Object} options Options forwarded to spawnSync (e.g. cwd, env, shell)
|
|
181
|
+
* @returns {Object} spawnSync result object with status, stdout, stderr, and error fields
|
|
182
|
+
*/
|
|
134
183
|
export function safeSpawnSync(command, args, options) {
|
|
135
184
|
if (
|
|
136
185
|
(isSecureMode && process.permission && !process.permission.has("child")) ||
|
|
@@ -146,6 +195,25 @@ export function safeSpawnSync(command, args, options) {
|
|
|
146
195
|
error: new Error("No execute permission"),
|
|
147
196
|
};
|
|
148
197
|
}
|
|
198
|
+
if (isSecureMode) {
|
|
199
|
+
if (isWindowsShellHijackRisk(command, options)) {
|
|
200
|
+
const blockedReason = `${command} matches local file in cwd (Windows shell hijack risk)`;
|
|
201
|
+
console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
|
|
202
|
+
return {
|
|
203
|
+
status: 1,
|
|
204
|
+
stdout: undefined,
|
|
205
|
+
stderr: undefined,
|
|
206
|
+
error: new Error(blockedReason),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (options?.cwd && options.cwd !== resolve(options.cwd)) {
|
|
210
|
+
if (DEBUG_MODE) {
|
|
211
|
+
console.log(
|
|
212
|
+
"Executing commands with a relative cwd can cause security issues.",
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
149
217
|
if (!options) {
|
|
150
218
|
options = {};
|
|
151
219
|
}
|
|
@@ -175,6 +243,40 @@ export function safeSpawnSync(command, args, options) {
|
|
|
175
243
|
);
|
|
176
244
|
}
|
|
177
245
|
}
|
|
246
|
+
let isPyPackageInstall = false;
|
|
247
|
+
if (command.includes("pip") && args?.includes("install")) {
|
|
248
|
+
isPyPackageInstall = true;
|
|
249
|
+
} else if (
|
|
250
|
+
command.includes("python") &&
|
|
251
|
+
args?.includes("pip") &&
|
|
252
|
+
args?.includes("install")
|
|
253
|
+
) {
|
|
254
|
+
isPyPackageInstall = true;
|
|
255
|
+
} else if (
|
|
256
|
+
command.includes("uv") &&
|
|
257
|
+
args?.includes("pip") &&
|
|
258
|
+
args?.includes("install")
|
|
259
|
+
) {
|
|
260
|
+
isPyPackageInstall = true;
|
|
261
|
+
}
|
|
262
|
+
if (isPyPackageInstall) {
|
|
263
|
+
const hasOnlyBinary = args?.some(
|
|
264
|
+
(arg) => arg === "--only-binary" || arg.startsWith("--only-binary="),
|
|
265
|
+
);
|
|
266
|
+
if (!hasOnlyBinary) {
|
|
267
|
+
if (isSecureMode) {
|
|
268
|
+
console.warn(
|
|
269
|
+
"\x1b[1;31mSecurity Alert: pip/uv install invoked without '--only-binary' argument in secure mode. This is a bug in cdxgen and introduces Arbitrary Code Execution (ACE) risks. Please report with an example repo here https://github.com/cdxgen/cdxgen/issues.\x1b[0m",
|
|
270
|
+
);
|
|
271
|
+
} else if (process.env?.CDXGEN_IN_CONTAINER === "true") {
|
|
272
|
+
console.log("Running pip/uv install without '--only-binary' argument.");
|
|
273
|
+
} else {
|
|
274
|
+
console.warn(
|
|
275
|
+
"\x1b[1;35mNotice: pip/uv install invoked without '--only-binary'. This allows executing untrusted setup.py scripts. Only run cdxgen in trusted directories.\x1b",
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
178
280
|
traceLog("spawn", { command, args, ...options });
|
|
179
281
|
commandsExecuted.add(command);
|
|
180
282
|
// Fix for DEP0190 warning
|
|
@@ -268,6 +370,12 @@ export const PREFER_MAVEN_DEPS_TREE = !["false", "0"].includes(
|
|
|
268
370
|
process.env?.PREFER_MAVEN_DEPS_TREE,
|
|
269
371
|
);
|
|
270
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Determines whether license information should be fetched from remote sources,
|
|
375
|
+
* based on the FETCH_LICENSE environment variable.
|
|
376
|
+
*
|
|
377
|
+
* @returns {boolean} True if the FETCH_LICENSE env var is set to "true" or "1"
|
|
378
|
+
*/
|
|
271
379
|
export function shouldFetchLicense() {
|
|
272
380
|
return (
|
|
273
381
|
process.env.FETCH_LICENSE &&
|
|
@@ -275,6 +383,12 @@ export function shouldFetchLicense() {
|
|
|
275
383
|
);
|
|
276
384
|
}
|
|
277
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Determines whether VCS (version control system) information should be fetched
|
|
388
|
+
* for Go packages, based on the GO_FETCH_VCS environment variable.
|
|
389
|
+
*
|
|
390
|
+
* @returns {boolean} True if the GO_FETCH_VCS env var is set to "true" or "1"
|
|
391
|
+
*/
|
|
278
392
|
export function shouldFetchVCS() {
|
|
279
393
|
return (
|
|
280
394
|
process.env.GO_FETCH_VCS && ["true", "1"].includes(process.env.GO_FETCH_VCS)
|
|
@@ -301,6 +415,12 @@ const MAX_LICENSE_ID_LENGTH = 100;
|
|
|
301
415
|
|
|
302
416
|
export const JAVA_CMD = getJavaCommand();
|
|
303
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Returns the Java executable command to use, resolved in priority order:
|
|
420
|
+
* JAVA_CMD env var > JAVA_HOME/bin/java > "java".
|
|
421
|
+
*
|
|
422
|
+
* @returns {string} Path or name of the Java executable
|
|
423
|
+
*/
|
|
304
424
|
export function getJavaCommand() {
|
|
305
425
|
let javaCmd = "java";
|
|
306
426
|
if (process.env.JAVA_CMD) {
|
|
@@ -317,6 +437,12 @@ export function getJavaCommand() {
|
|
|
317
437
|
|
|
318
438
|
export const PYTHON_CMD = getPythonCommand();
|
|
319
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Returns the Python executable command to use, resolved in priority order:
|
|
442
|
+
* PYTHON_CMD env var > CONDA_PYTHON_EXE env var > "python".
|
|
443
|
+
*
|
|
444
|
+
* @returns {string} Path or name of the Python executable
|
|
445
|
+
*/
|
|
320
446
|
export function getPythonCommand() {
|
|
321
447
|
let pythonCmd = "python";
|
|
322
448
|
if (process.env.PYTHON_CMD) {
|
|
@@ -451,7 +577,6 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
451
577
|
"typescript",
|
|
452
578
|
"ts",
|
|
453
579
|
"tsx",
|
|
454
|
-
"vsix",
|
|
455
580
|
"yarn",
|
|
456
581
|
"rush",
|
|
457
582
|
],
|
|
@@ -471,7 +596,11 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
471
596
|
"poetry",
|
|
472
597
|
"uv",
|
|
473
598
|
"pdm",
|
|
599
|
+
"rye",
|
|
474
600
|
"hatch",
|
|
601
|
+
"conda",
|
|
602
|
+
"miniconda",
|
|
603
|
+
"pyenv",
|
|
475
604
|
],
|
|
476
605
|
go: ["go", "golang", "gomod", "gopkg"],
|
|
477
606
|
rust: ["rust", "rust-lang", "cargo"],
|
|
@@ -542,6 +671,14 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
542
671
|
scala: ["scala", "scala3", "sbt", "mill"],
|
|
543
672
|
nix: ["nix", "nixos", "flake"],
|
|
544
673
|
caxa: ["caxa"],
|
|
674
|
+
"vscode-extension": [
|
|
675
|
+
"vscode-extension",
|
|
676
|
+
"vsix",
|
|
677
|
+
"vscode",
|
|
678
|
+
"openvsx",
|
|
679
|
+
"vscode-extensions",
|
|
680
|
+
"ide-extensions",
|
|
681
|
+
],
|
|
545
682
|
};
|
|
546
683
|
|
|
547
684
|
// Package manager aliases
|
|
@@ -1119,6 +1256,13 @@ export function readLicenseText(licenseFilepath, licenseContentType) {
|
|
|
1119
1256
|
return null;
|
|
1120
1257
|
}
|
|
1121
1258
|
|
|
1259
|
+
/**
|
|
1260
|
+
* Fetches license information for a list of Swift packages by querying the
|
|
1261
|
+
* GitHub repository license API for packages hosted on github.com.
|
|
1262
|
+
*
|
|
1263
|
+
* @param {Object[]} pkgList List of Swift package objects with optional repository.url fields
|
|
1264
|
+
* @returns {Promise<Object[]>} Resolved list of package objects, each augmented with a license field where available
|
|
1265
|
+
*/
|
|
1122
1266
|
export async function getSwiftPackageMetadata(pkgList) {
|
|
1123
1267
|
const cdepList = [];
|
|
1124
1268
|
for (const p of pkgList) {
|
|
@@ -1203,8 +1347,13 @@ export async function getNpmMetadata(pkgList) {
|
|
|
1203
1347
|
*
|
|
1204
1348
|
* @param {string} pkgJsonFile package.json file
|
|
1205
1349
|
* @param {boolean} simple Return a simpler representation of the component by skipping extended attributes and license fetch.
|
|
1350
|
+
* @param {boolean} securityProps Collect security-related properties
|
|
1206
1351
|
*/
|
|
1207
|
-
export async function parsePkgJson(
|
|
1352
|
+
export async function parsePkgJson(
|
|
1353
|
+
pkgJsonFile,
|
|
1354
|
+
simple = false,
|
|
1355
|
+
securityProps = false,
|
|
1356
|
+
) {
|
|
1208
1357
|
const pkgList = [];
|
|
1209
1358
|
if (safeExistsSync(pkgJsonFile)) {
|
|
1210
1359
|
try {
|
|
@@ -1267,6 +1416,107 @@ export async function parsePkgJson(pkgJsonFile, simple = false) {
|
|
|
1267
1416
|
},
|
|
1268
1417
|
};
|
|
1269
1418
|
}
|
|
1419
|
+
if (securityProps) {
|
|
1420
|
+
if (!apkg.properties) {
|
|
1421
|
+
apkg.properties = [];
|
|
1422
|
+
}
|
|
1423
|
+
// Track executable binaries (potential code execution vectors)
|
|
1424
|
+
if (pkgData.bin) {
|
|
1425
|
+
const binValue =
|
|
1426
|
+
typeof pkgData.bin === "object"
|
|
1427
|
+
? Object.keys(pkgData.bin).join(", ")
|
|
1428
|
+
: pkgData.bin;
|
|
1429
|
+
apkg.properties.push({
|
|
1430
|
+
name: "cdx:npm:bin",
|
|
1431
|
+
value: binValue,
|
|
1432
|
+
});
|
|
1433
|
+
apkg.properties.push({
|
|
1434
|
+
name: "cdx:npm:has_binary",
|
|
1435
|
+
value: "true",
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
// Track lifecycle scripts (preinstall, postinstall, etc. - code execution risk)
|
|
1439
|
+
if (pkgData.scripts && Object.keys(pkgData.scripts).length) {
|
|
1440
|
+
const scriptNames = Object.keys(pkgData.scripts).join(", ");
|
|
1441
|
+
apkg.properties.push({
|
|
1442
|
+
name: "cdx:npm:scripts",
|
|
1443
|
+
value: scriptNames,
|
|
1444
|
+
});
|
|
1445
|
+
// Flag high-risk scripts specifically
|
|
1446
|
+
const riskyScripts = [
|
|
1447
|
+
"preinstall",
|
|
1448
|
+
"install",
|
|
1449
|
+
"postinstall",
|
|
1450
|
+
"prepublish",
|
|
1451
|
+
"prepare",
|
|
1452
|
+
].filter((script) => pkgData.scripts[script]);
|
|
1453
|
+
if (riskyScripts.length) {
|
|
1454
|
+
apkg.properties.push({
|
|
1455
|
+
name: "cdx:npm:risky_scripts",
|
|
1456
|
+
value: riskyScripts.join(", "),
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
// Track platform/architecture constraints
|
|
1461
|
+
if (pkgData.cpu && Array.isArray(pkgData.cpu) && pkgData.cpu.length) {
|
|
1462
|
+
apkg.properties.push({
|
|
1463
|
+
name: "cdx:npm:cpu",
|
|
1464
|
+
value: pkgData.cpu.join(", "),
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
if (pkgData.os && Array.isArray(pkgData.os) && pkgData.os.length) {
|
|
1468
|
+
apkg.properties.push({
|
|
1469
|
+
name: "cdx:npm:os",
|
|
1470
|
+
value: pkgData.os.join(", "),
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
if (
|
|
1474
|
+
pkgData.libc &&
|
|
1475
|
+
Array.isArray(pkgData.libc) &&
|
|
1476
|
+
pkgData.libc.length
|
|
1477
|
+
) {
|
|
1478
|
+
apkg.properties.push({
|
|
1479
|
+
name: "cdx:npm:libc",
|
|
1480
|
+
value: pkgData.libc.join(", "),
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
// Track deprecation notices
|
|
1484
|
+
if (pkgData.deprecated) {
|
|
1485
|
+
apkg.properties.push({
|
|
1486
|
+
name: "cdx:npm:deprecation_notice",
|
|
1487
|
+
value: pkgData.deprecated,
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
// Track if package uses node-gyp (native C/C++ addons = higher risk)
|
|
1491
|
+
if (
|
|
1492
|
+
pkgData.gypfile === true ||
|
|
1493
|
+
pkgData.files?.some((f) => f.endsWith(".gyp") || f.endsWith(".gypi"))
|
|
1494
|
+
) {
|
|
1495
|
+
apkg.properties.push({
|
|
1496
|
+
name: "cdx:npm:gypfile",
|
|
1497
|
+
value: "true",
|
|
1498
|
+
});
|
|
1499
|
+
apkg.properties.push({
|
|
1500
|
+
name: "cdx:npm:native_addon",
|
|
1501
|
+
value: "true",
|
|
1502
|
+
});
|
|
1503
|
+
const nativeDeps = [
|
|
1504
|
+
"nan",
|
|
1505
|
+
"node-addon-api",
|
|
1506
|
+
"bindings",
|
|
1507
|
+
"node-gyp-build",
|
|
1508
|
+
];
|
|
1509
|
+
const foundNativeDeps = Object.keys(
|
|
1510
|
+
pkgData.dependencies || {},
|
|
1511
|
+
).filter((dep) => nativeDeps.includes(dep));
|
|
1512
|
+
if (foundNativeDeps.length) {
|
|
1513
|
+
apkg.properties.push({
|
|
1514
|
+
name: "cdx:npm:native_deps",
|
|
1515
|
+
value: foundNativeDeps.join(", "),
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1270
1520
|
pkgList.push(apkg);
|
|
1271
1521
|
} catch (_err) {
|
|
1272
1522
|
// continue regardless of error
|
|
@@ -1411,7 +1661,10 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1411
1661
|
name: "ResolvedUrl",
|
|
1412
1662
|
value: node.resolved,
|
|
1413
1663
|
});
|
|
1414
|
-
pkg.
|
|
1664
|
+
pkg.externalReferences.push({
|
|
1665
|
+
type: "distribution",
|
|
1666
|
+
url: node.resolved,
|
|
1667
|
+
});
|
|
1415
1668
|
}
|
|
1416
1669
|
}
|
|
1417
1670
|
if (node.location) {
|
|
@@ -1692,7 +1945,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1692
1945
|
if (!targetVersion || !targetName) {
|
|
1693
1946
|
if (pkgSpecVersionCache[`${edge.name}-${edge.spec}`]) {
|
|
1694
1947
|
targetVersion = pkgSpecVersionCache[`${edge.name}-${edge.spec}`];
|
|
1695
|
-
targetName = edge.name;
|
|
1948
|
+
targetName = edge.name.replace(/-cjs$/, "");
|
|
1696
1949
|
}
|
|
1697
1950
|
}
|
|
1698
1951
|
}
|
|
@@ -2735,6 +2988,13 @@ function findMatchingWorkspace(workspacePackages, packageName) {
|
|
|
2735
2988
|
);
|
|
2736
2989
|
}
|
|
2737
2990
|
|
|
2991
|
+
/**
|
|
2992
|
+
* Parses the workspaces field from a package.json file and returns the list of
|
|
2993
|
+
* workspace glob patterns. Handles both array and object (with packages key) formats.
|
|
2994
|
+
*
|
|
2995
|
+
* @param {string} packageJsonFile Path to the package.json file to parse
|
|
2996
|
+
* @returns {Object} Object with a packages array of workspace glob patterns, or an empty object on error
|
|
2997
|
+
*/
|
|
2738
2998
|
export function parseYarnWorkspace(packageJsonFile) {
|
|
2739
2999
|
try {
|
|
2740
3000
|
const packageData = JSON.parse(readFileSync(packageJsonFile, "utf-8"));
|
|
@@ -2830,45 +3090,31 @@ export function findPnpmPackagePath(baseDir, packageName, version) {
|
|
|
2830
3090
|
* @returns {Array} Enhanced package list
|
|
2831
3091
|
*/
|
|
2832
3092
|
export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
2833
|
-
if (!pkgList
|
|
3093
|
+
if (!pkgList?.length || !lockFilePath) {
|
|
2834
3094
|
return pkgList;
|
|
2835
3095
|
}
|
|
2836
|
-
|
|
2837
3096
|
const baseDir = dirname(lockFilePath);
|
|
2838
3097
|
const nodeModulesDir = join(baseDir, "node_modules");
|
|
2839
|
-
|
|
2840
|
-
// Only proceed if node_modules exists
|
|
2841
3098
|
if (!safeExistsSync(nodeModulesDir)) {
|
|
2842
3099
|
return pkgList;
|
|
2843
3100
|
}
|
|
2844
|
-
|
|
2845
3101
|
if (DEBUG_MODE) {
|
|
2846
3102
|
console.log(
|
|
2847
3103
|
`Metadata for ${pkgList.length} pnpm packages using local node_modules at ${nodeModulesDir}`,
|
|
2848
3104
|
);
|
|
2849
3105
|
}
|
|
2850
|
-
|
|
2851
3106
|
let enhancedCount = 0;
|
|
2852
3107
|
for (const pkg of pkgList) {
|
|
2853
|
-
// Skip if package already has complete metadata
|
|
2854
|
-
if (pkg.description && pkg.author && pkg.license) {
|
|
2855
|
-
continue;
|
|
2856
|
-
}
|
|
2857
|
-
|
|
2858
|
-
// Find the package path in node_modules
|
|
2859
3108
|
const packagePath = findPnpmPackagePath(baseDir, pkg.name, pkg.version);
|
|
2860
3109
|
if (!packagePath) {
|
|
2861
3110
|
continue;
|
|
2862
3111
|
}
|
|
2863
|
-
|
|
2864
3112
|
const packageJsonPath = join(packagePath, "package.json");
|
|
2865
3113
|
if (!safeExistsSync(packageJsonPath)) {
|
|
2866
3114
|
continue;
|
|
2867
3115
|
}
|
|
2868
|
-
|
|
2869
3116
|
try {
|
|
2870
|
-
|
|
2871
|
-
const localPkgList = await parsePkgJson(packageJsonPath, true);
|
|
3117
|
+
const localPkgList = await parsePkgJson(packageJsonPath, true, true);
|
|
2872
3118
|
if (localPkgList && localPkgList.length === 1) {
|
|
2873
3119
|
const localMetadata = localPkgList[0];
|
|
2874
3120
|
if (localMetadata && Object.keys(localMetadata).length) {
|
|
@@ -2887,16 +3133,27 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
|
2887
3133
|
if (!pkg.repository && localMetadata.repository) {
|
|
2888
3134
|
pkg.repository = localMetadata.repository;
|
|
2889
3135
|
}
|
|
2890
|
-
|
|
2891
|
-
// Add a property to track that we enhanced from local node_modules
|
|
2892
3136
|
if (!pkg.properties) {
|
|
2893
3137
|
pkg.properties = [];
|
|
2894
3138
|
}
|
|
3139
|
+
if (localMetadata?.properties?.length) {
|
|
3140
|
+
const seenProperties = new Set(
|
|
3141
|
+
pkg.properties.map(
|
|
3142
|
+
(prop) => `${String(prop?.name)}\u0000${String(prop?.value)}`,
|
|
3143
|
+
),
|
|
3144
|
+
);
|
|
3145
|
+
for (const prop of localMetadata.properties) {
|
|
3146
|
+
const propertyKey = `${String(prop?.name)}\u0000${String(prop?.value)}`;
|
|
3147
|
+
if (!seenProperties.has(propertyKey)) {
|
|
3148
|
+
pkg.properties.push(prop);
|
|
3149
|
+
seenProperties.add(propertyKey);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
2895
3153
|
pkg.properties.push({
|
|
2896
3154
|
name: "LocalNodeModulesPath",
|
|
2897
3155
|
value: packagePath,
|
|
2898
3156
|
});
|
|
2899
|
-
|
|
2900
3157
|
enhancedCount++;
|
|
2901
3158
|
}
|
|
2902
3159
|
}
|
|
@@ -2910,13 +3167,11 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
|
2910
3167
|
}
|
|
2911
3168
|
}
|
|
2912
3169
|
}
|
|
2913
|
-
|
|
2914
3170
|
if (DEBUG_MODE && enhancedCount > 0) {
|
|
2915
3171
|
console.log(
|
|
2916
3172
|
`Enhanced metadata for ${enhancedCount} packages from local node_modules`,
|
|
2917
3173
|
);
|
|
2918
3174
|
}
|
|
2919
|
-
|
|
2920
3175
|
return pkgList;
|
|
2921
3176
|
}
|
|
2922
3177
|
|
|
@@ -2979,10 +3234,18 @@ export async function parsePnpmLock(
|
|
|
2979
3234
|
}
|
|
2980
3235
|
if (safeExistsSync(pnpmLock)) {
|
|
2981
3236
|
const lockData = readFileSync(pnpmLock, "utf8");
|
|
2982
|
-
|
|
3237
|
+
let yamlObj = parseAllDocuments(lockData);
|
|
2983
3238
|
if (!yamlObj) {
|
|
2984
3239
|
return {};
|
|
2985
3240
|
}
|
|
3241
|
+
if (Array.isArray(yamlObj)) {
|
|
3242
|
+
try {
|
|
3243
|
+
yamlObj = yamlObj[yamlObj.length - 1].toJS();
|
|
3244
|
+
} catch (_e) {
|
|
3245
|
+
console.log(`Unable to parse the pnpm lock file ${pnpmLock}.`);
|
|
3246
|
+
return {};
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
2986
3249
|
lockfileVersion = yamlObj.lockfileVersion;
|
|
2987
3250
|
try {
|
|
2988
3251
|
lockfileVersion = Number.parseFloat(lockfileVersion, 10);
|
|
@@ -3281,6 +3544,7 @@ export async function parsePnpmLock(
|
|
|
3281
3544
|
packages[fullName]?.resolution ||
|
|
3282
3545
|
snapshots[fullName]?.resolution;
|
|
3283
3546
|
const integrity = resolution?.integrity;
|
|
3547
|
+
const tarball = resolution?.tarball;
|
|
3284
3548
|
const cpu =
|
|
3285
3549
|
packages[pkgKeys[k]]?.cpu ||
|
|
3286
3550
|
snapshots[pkgKeys[k]]?.cpu ||
|
|
@@ -3499,10 +3763,10 @@ export async function parsePnpmLock(
|
|
|
3499
3763
|
value: pnpmLock,
|
|
3500
3764
|
},
|
|
3501
3765
|
];
|
|
3502
|
-
if (hasBin
|
|
3766
|
+
if (hasBin) {
|
|
3503
3767
|
properties.push({
|
|
3504
3768
|
name: "cdx:npm:has_binary",
|
|
3505
|
-
value:
|
|
3769
|
+
value: "true",
|
|
3506
3770
|
});
|
|
3507
3771
|
}
|
|
3508
3772
|
if (deprecatedMessage) {
|
|
@@ -3515,7 +3779,7 @@ export async function parsePnpmLock(
|
|
|
3515
3779
|
Object.entries(binary_metadata).forEach(([key, value]) => {
|
|
3516
3780
|
if (!value) return;
|
|
3517
3781
|
properties.push({
|
|
3518
|
-
name: `cdx:
|
|
3782
|
+
name: `cdx:npm:${key}`,
|
|
3519
3783
|
value: Array.isArray(value) ? value.join(", ") : value,
|
|
3520
3784
|
});
|
|
3521
3785
|
});
|
|
@@ -3613,6 +3877,14 @@ export async function parsePnpmLock(
|
|
|
3613
3877
|
},
|
|
3614
3878
|
},
|
|
3615
3879
|
};
|
|
3880
|
+
if (tarball) {
|
|
3881
|
+
thePkg.externalReferences = [
|
|
3882
|
+
{
|
|
3883
|
+
type: "distribution",
|
|
3884
|
+
url: tarball,
|
|
3885
|
+
},
|
|
3886
|
+
];
|
|
3887
|
+
}
|
|
3616
3888
|
// Don't add internal workspace packages to the components list
|
|
3617
3889
|
if (thePkg.type !== "application") {
|
|
3618
3890
|
pkgList.push(thePkg);
|
|
@@ -4646,6 +4918,15 @@ export function parseLeinDep(rawOutput) {
|
|
|
4646
4918
|
return [];
|
|
4647
4919
|
}
|
|
4648
4920
|
|
|
4921
|
+
/**
|
|
4922
|
+
* Recursively walks a parsed EDN map node produced by the Leiningen dependency
|
|
4923
|
+
* tree and collects unique dependency entries into the deps array.
|
|
4924
|
+
*
|
|
4925
|
+
* @param {Object} node Parsed EDN node (expected to have a "map" property)
|
|
4926
|
+
* @param {Object} keys_cache Cache object used to deduplicate entries by group-name-version key
|
|
4927
|
+
* @param {Object[]} deps Accumulator array of dependency objects with group, name, and version fields
|
|
4928
|
+
* @returns {Object[]} The populated deps array
|
|
4929
|
+
*/
|
|
4649
4930
|
export function parseLeinMap(node, keys_cache, deps) {
|
|
4650
4931
|
if (node["map"]) {
|
|
4651
4932
|
for (const n of node["map"]) {
|
|
@@ -5096,7 +5377,7 @@ export async function getMvnMetadata(
|
|
|
5096
5377
|
const ANDROID_MAVEN_URL =
|
|
5097
5378
|
process.env.ANDROID_MAVEN_URL || "https://maven.google.com/";
|
|
5098
5379
|
const cdepList = [];
|
|
5099
|
-
if (!pkgList
|
|
5380
|
+
if (!pkgList?.length) {
|
|
5100
5381
|
return pkgList;
|
|
5101
5382
|
}
|
|
5102
5383
|
if (DEBUG_MODE && shouldFetchLicense()) {
|
|
@@ -5390,7 +5671,7 @@ export async function getPyMetadata(pkgList, fetchDepsInfo) {
|
|
|
5390
5671
|
const PYPI_URL = process.env.PYPI_URL || "https://pypi.org/pypi/";
|
|
5391
5672
|
const cdepList = [];
|
|
5392
5673
|
for (const p of pkgList) {
|
|
5393
|
-
if (!p
|
|
5674
|
+
if (!p?.name) {
|
|
5394
5675
|
continue;
|
|
5395
5676
|
}
|
|
5396
5677
|
try {
|
|
@@ -5474,7 +5755,7 @@ export async function getPyMetadata(pkgList, fetchDepsInfo) {
|
|
|
5474
5755
|
}
|
|
5475
5756
|
}
|
|
5476
5757
|
// Use the latest version if none specified
|
|
5477
|
-
if (!p.version
|
|
5758
|
+
if (!p.version?.trim().length) {
|
|
5478
5759
|
let versionSpecifiers;
|
|
5479
5760
|
if (p.properties?.length) {
|
|
5480
5761
|
for (const pprop of p.properties) {
|
|
@@ -5629,6 +5910,9 @@ export function parseBdistMetadata(mDataFile, rawMetadata = undefined) {
|
|
|
5629
5910
|
externalReferences: [],
|
|
5630
5911
|
properties: [],
|
|
5631
5912
|
};
|
|
5913
|
+
if (mDataFile) {
|
|
5914
|
+
pkg.properties.push({ name: "SrcFile", value: mDataFile });
|
|
5915
|
+
}
|
|
5632
5916
|
const lines = mData.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
5633
5917
|
let isBody = false;
|
|
5634
5918
|
for (const line of lines) {
|
|
@@ -6467,7 +6751,8 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6467
6751
|
},
|
|
6468
6752
|
}
|
|
6469
6753
|
: undefined;
|
|
6470
|
-
const
|
|
6754
|
+
const normalizedData = reqData.replace(/\r/g, "").replace(/\\\n/g, " ");
|
|
6755
|
+
const lines = normalizedData.split("\n");
|
|
6471
6756
|
for (const line of lines) {
|
|
6472
6757
|
let l = line.trim();
|
|
6473
6758
|
if (l.includes("# Basic requirements")) {
|
|
@@ -6495,7 +6780,25 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6495
6780
|
},
|
|
6496
6781
|
]
|
|
6497
6782
|
: [];
|
|
6498
|
-
|
|
6783
|
+
const hashes = [];
|
|
6784
|
+
const hashRegex = /--hash=([a-zA-Z0-9\-]+):([a-fA-F0-9]+)/g;
|
|
6785
|
+
let hashMatch;
|
|
6786
|
+
while ((hashMatch = hashRegex.exec(l)) !== null) {
|
|
6787
|
+
let alg = hashMatch[1].toUpperCase();
|
|
6788
|
+
if (alg === "SHA256") alg = "SHA-256";
|
|
6789
|
+
else if (alg === "SHA384") alg = "SHA-384";
|
|
6790
|
+
else if (alg === "SHA512") alg = "SHA-512";
|
|
6791
|
+
else if (alg === "SHA1") alg = "SHA-1";
|
|
6792
|
+
hashes.push({
|
|
6793
|
+
alg: alg,
|
|
6794
|
+
content: hashMatch[2],
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6797
|
+
// Strip the hash flags and any residual backslashes
|
|
6798
|
+
l = l
|
|
6799
|
+
.replace(/--hash=[a-zA-Z0-9\-]+:[a-fA-F0-9]+/g, "")
|
|
6800
|
+
.replace(/\\/g, "")
|
|
6801
|
+
.trim();
|
|
6499
6802
|
// Handle markers
|
|
6500
6803
|
let markers = null;
|
|
6501
6804
|
let structuredMarkers = null;
|
|
@@ -6535,6 +6838,9 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6535
6838
|
scope: compScope,
|
|
6536
6839
|
evidence,
|
|
6537
6840
|
};
|
|
6841
|
+
if (hashes.length > 0) {
|
|
6842
|
+
apkg.hashes = hashes;
|
|
6843
|
+
}
|
|
6538
6844
|
if (comment) {
|
|
6539
6845
|
apkg.licenses = comment
|
|
6540
6846
|
.split("/")
|
|
@@ -7238,6 +7544,16 @@ async function getGoPkgVCSUrl(group, name) {
|
|
|
7238
7544
|
return undefined;
|
|
7239
7545
|
}
|
|
7240
7546
|
|
|
7547
|
+
/**
|
|
7548
|
+
* Builds a Go package component object containing purl, bom-ref, integrity hash,
|
|
7549
|
+
* and optionally license and VCS external reference information.
|
|
7550
|
+
*
|
|
7551
|
+
* @param {string} group Package group (module path prefix, may be empty)
|
|
7552
|
+
* @param {string} name Package name (full module path when group is empty)
|
|
7553
|
+
* @param {string} version Package version string
|
|
7554
|
+
* @param {string} hash Integrity hash (e.g. "sha256-…"), used as _integrity
|
|
7555
|
+
* @returns {Promise<Object>} Component object ready for inclusion in a BOM package list
|
|
7556
|
+
*/
|
|
7241
7557
|
export async function getGoPkgComponent(group, name, version, hash) {
|
|
7242
7558
|
let license;
|
|
7243
7559
|
if (shouldFetchLicense()) {
|
|
@@ -7421,6 +7737,15 @@ export async function parseGoModData(goModData, gosumMap) {
|
|
|
7421
7737
|
};
|
|
7422
7738
|
}
|
|
7423
7739
|
|
|
7740
|
+
/**
|
|
7741
|
+
* Parses a Go modules text file (e.g. vendor/modules.txt) and returns a list of
|
|
7742
|
+
* Go package components. Cross-references the go.sum map for integrity hashes and
|
|
7743
|
+
* sets scope and confidence based on hash availability.
|
|
7744
|
+
*
|
|
7745
|
+
* @param {string} txtFile Path to the modules.txt file
|
|
7746
|
+
* @param {Object} gosumMap Map of "module@version" keys to sha256 hash values from go.sum
|
|
7747
|
+
* @returns {Promise<Object[]>} List of Go package component objects with evidence
|
|
7748
|
+
*/
|
|
7424
7749
|
export async function parseGoModulesTxt(txtFile, gosumMap) {
|
|
7425
7750
|
const pkgList = [];
|
|
7426
7751
|
const txtData = readFileSync(txtFile, { encoding: "utf-8" });
|
|
@@ -7824,6 +8149,14 @@ export async function parseGosumData(gosumData) {
|
|
|
7824
8149
|
return pkgList;
|
|
7825
8150
|
}
|
|
7826
8151
|
|
|
8152
|
+
/**
|
|
8153
|
+
* Parses the contents of a Gopkg.lock or Gopkg.toml file (dep tool format) and
|
|
8154
|
+
* returns a list of Go package components. Optionally fetches license information
|
|
8155
|
+
* for each package when FETCH_LICENSE is enabled.
|
|
8156
|
+
*
|
|
8157
|
+
* @param {string} gopkgData Raw string contents of the Gopkg lock/toml file
|
|
8158
|
+
* @returns {Promise<Object[]>} List of Go package component objects
|
|
8159
|
+
*/
|
|
7827
8160
|
export async function parseGopkgData(gopkgData) {
|
|
7828
8161
|
const pkgList = [];
|
|
7829
8162
|
if (!gopkgData) {
|
|
@@ -7873,6 +8206,13 @@ export async function parseGopkgData(gopkgData) {
|
|
|
7873
8206
|
return pkgList;
|
|
7874
8207
|
}
|
|
7875
8208
|
|
|
8209
|
+
/**
|
|
8210
|
+
* Parses the output of `go version -m` (build info) and returns a list of Go
|
|
8211
|
+
* package components for each "dep" line, including name, version, and integrity hash.
|
|
8212
|
+
*
|
|
8213
|
+
* @param {string} buildInfoData Raw string output from `go version -m`
|
|
8214
|
+
* @returns {Promise<Object[]>} List of Go package component objects
|
|
8215
|
+
*/
|
|
7876
8216
|
export async function parseGoVersionData(buildInfoData) {
|
|
7877
8217
|
const pkgList = [];
|
|
7878
8218
|
if (!buildInfoData) {
|
|
@@ -9199,6 +9539,13 @@ export async function parseCargoData(
|
|
|
9199
9539
|
return pkgList;
|
|
9200
9540
|
}
|
|
9201
9541
|
|
|
9542
|
+
/**
|
|
9543
|
+
* Parses a Cargo.lock file's TOML data and returns a flat dependency graph as an
|
|
9544
|
+
* array of objects mapping each package purl to the purls it directly depends on.
|
|
9545
|
+
*
|
|
9546
|
+
* @param {string} cargoLockData Raw TOML string contents of a Cargo.lock file
|
|
9547
|
+
* @returns {Object[]} Array of dependency relationship objects with ref and dependsOn fields
|
|
9548
|
+
*/
|
|
9202
9549
|
export function parseCargoDependencyData(cargoLockData) {
|
|
9203
9550
|
const purlFromPackageInfo = (pkg) =>
|
|
9204
9551
|
decodeURIComponent(
|
|
@@ -9258,6 +9605,14 @@ export function parseCargoDependencyData(cargoLockData) {
|
|
|
9258
9605
|
return result;
|
|
9259
9606
|
}
|
|
9260
9607
|
|
|
9608
|
+
/**
|
|
9609
|
+
* Parses tab-separated cargo-auditable binary metadata output and returns a list
|
|
9610
|
+
* of Rust package components. Optionally fetches crates.io metadata when
|
|
9611
|
+
* FETCH_LICENSE is enabled.
|
|
9612
|
+
*
|
|
9613
|
+
* @param {string} cargoData Tab-separated string output from cargo-auditable or similar tool
|
|
9614
|
+
* @returns {Promise<Object[]>} List of Rust package component objects with group, name, and version
|
|
9615
|
+
*/
|
|
9261
9616
|
export async function parseCargoAuditableData(cargoData) {
|
|
9262
9617
|
const pkgList = [];
|
|
9263
9618
|
if (!cargoData) {
|
|
@@ -9360,6 +9715,13 @@ export async function parsePubLockData(pubLockData, lockFile) {
|
|
|
9360
9715
|
return { rootList, pkgList };
|
|
9361
9716
|
}
|
|
9362
9717
|
|
|
9718
|
+
/**
|
|
9719
|
+
* Parses a Dart pub package's pubspec.yaml content and returns a list containing
|
|
9720
|
+
* a single component object with name, description, version, homepage, and purl.
|
|
9721
|
+
*
|
|
9722
|
+
* @param {string} pubYamlData Raw YAML string contents of a pubspec.yaml file
|
|
9723
|
+
* @returns {Object[]} List containing a single Dart package component object
|
|
9724
|
+
*/
|
|
9363
9725
|
export function parsePubYamlData(pubYamlData) {
|
|
9364
9726
|
const pkgList = [];
|
|
9365
9727
|
let yamlObj;
|
|
@@ -9386,6 +9748,14 @@ export function parsePubYamlData(pubYamlData) {
|
|
|
9386
9748
|
return pkgList;
|
|
9387
9749
|
}
|
|
9388
9750
|
|
|
9751
|
+
/**
|
|
9752
|
+
* Parses Helm chart YAML data (Chart.yaml or repository index.yaml) and returns
|
|
9753
|
+
* a list of Helm chart component objects including the chart itself and any
|
|
9754
|
+
* declared dependencies or index entries.
|
|
9755
|
+
*
|
|
9756
|
+
* @param {string} helmData Raw YAML string contents of a Helm Chart.yaml or index.yaml file
|
|
9757
|
+
* @returns {Object[]} List of Helm chart component objects with name, version, and optional homepage/repository
|
|
9758
|
+
*/
|
|
9389
9759
|
export function parseHelmYamlData(helmData) {
|
|
9390
9760
|
const pkgList = [];
|
|
9391
9761
|
let yamlObj;
|
|
@@ -9451,6 +9821,17 @@ export function parseHelmYamlData(helmData) {
|
|
|
9451
9821
|
return pkgList;
|
|
9452
9822
|
}
|
|
9453
9823
|
|
|
9824
|
+
/**
|
|
9825
|
+
* Recursively walks a parsed YAML/JSON object structure to find container image
|
|
9826
|
+
* references stored under common keys (image, repository, dockerImage, etc.) and
|
|
9827
|
+
* appends discovered image and service entries to pkgList while tracking seen
|
|
9828
|
+
* images in imgList to avoid duplicates.
|
|
9829
|
+
*
|
|
9830
|
+
* @param {Object|Array|string} keyValueObj The object, array, or string node to inspect
|
|
9831
|
+
* @param {Object[]} pkgList Accumulator array that receives {image} and {service} entries
|
|
9832
|
+
* @param {string[]} imgList Accumulator array of image name strings already seen
|
|
9833
|
+
* @returns {string[]} The updated imgList
|
|
9834
|
+
*/
|
|
9454
9835
|
export function recurseImageNameLookup(keyValueObj, pkgList, imgList) {
|
|
9455
9836
|
if (typeof keyValueObj === "string" || keyValueObj instanceof String) {
|
|
9456
9837
|
return imgList;
|
|
@@ -9536,6 +9917,14 @@ function substituteBuildArgs(statement, buildArgs) {
|
|
|
9536
9917
|
return statement;
|
|
9537
9918
|
}
|
|
9538
9919
|
|
|
9920
|
+
/**
|
|
9921
|
+
* Parses the contents of a Dockerfile or Containerfile and returns a list of
|
|
9922
|
+
* base image objects referenced by FROM instructions, substituting ARG default
|
|
9923
|
+
* values where possible and skipping multi-stage build alias references.
|
|
9924
|
+
*
|
|
9925
|
+
* @param {string} fileContents Raw string contents of the Dockerfile/Containerfile
|
|
9926
|
+
* @returns {Object[]} Array of objects with an image property for each unique base image
|
|
9927
|
+
*/
|
|
9539
9928
|
export function parseContainerFile(fileContents) {
|
|
9540
9929
|
const buildArgs = new Map();
|
|
9541
9930
|
const imagesSet = new Set();
|
|
@@ -9603,6 +9992,13 @@ export function parseContainerFile(fileContents) {
|
|
|
9603
9992
|
});
|
|
9604
9993
|
}
|
|
9605
9994
|
|
|
9995
|
+
/**
|
|
9996
|
+
* Parses a Bitbucket Pipelines YAML file and extracts all Docker image references
|
|
9997
|
+
* used as build environments and pipe references (docker:// pipes are normalized).
|
|
9998
|
+
*
|
|
9999
|
+
* @param {string} fileContents Raw string contents of the bitbucket-pipelines.yml file
|
|
10000
|
+
* @returns {Object[]} Array of objects with an image property for each referenced image or pipe
|
|
10001
|
+
*/
|
|
9606
10002
|
export function parseBitbucketPipelinesFile(fileContents) {
|
|
9607
10003
|
const imgList = [];
|
|
9608
10004
|
|
|
@@ -9664,6 +10060,14 @@ export function parseBitbucketPipelinesFile(fileContents) {
|
|
|
9664
10060
|
return imgList;
|
|
9665
10061
|
}
|
|
9666
10062
|
|
|
10063
|
+
/**
|
|
10064
|
+
* Parses container specification data such as Docker Compose files, Kubernetes
|
|
10065
|
+
* manifests, Tekton tasks, Skaffold configs, or Kustomize overlays (YAML, possibly
|
|
10066
|
+
* multi-document) and returns a list of image, service, and OCI spec entries.
|
|
10067
|
+
*
|
|
10068
|
+
* @param {string} dcData Raw YAML string contents of the container spec file
|
|
10069
|
+
* @returns {Object[]} Array of objects with image, service, or ociSpec properties
|
|
10070
|
+
*/
|
|
9667
10071
|
export function parseContainerSpecData(dcData) {
|
|
9668
10072
|
const pkgList = [];
|
|
9669
10073
|
const imgList = [];
|
|
@@ -9731,6 +10135,14 @@ export function parseContainerSpecData(dcData) {
|
|
|
9731
10135
|
return pkgList;
|
|
9732
10136
|
}
|
|
9733
10137
|
|
|
10138
|
+
/**
|
|
10139
|
+
* Identifies the data flow direction of a Privado processing object based on its
|
|
10140
|
+
* sinkId value: "write" sinks map to "inbound", "read" sinks to "outbound", and
|
|
10141
|
+
* HTTP/gRPC sinks to "bi-directional".
|
|
10142
|
+
*
|
|
10143
|
+
* @param {Object} processingObj Privado processing object, expected to have a sinkId property
|
|
10144
|
+
* @returns {string} Flow direction string: "inbound", "outbound", "bi-directional", or "unknown"
|
|
10145
|
+
*/
|
|
9734
10146
|
export function identifyFlow(processingObj) {
|
|
9735
10147
|
let flow = "unknown";
|
|
9736
10148
|
if (processingObj.sinkId) {
|
|
@@ -9757,6 +10169,14 @@ function convertProcessing(processing_list) {
|
|
|
9757
10169
|
return data_list;
|
|
9758
10170
|
}
|
|
9759
10171
|
|
|
10172
|
+
/**
|
|
10173
|
+
* Parses a Privado data flow JSON file and returns a list of service objects
|
|
10174
|
+
* enriched with data classifications, endpoints, trust-boundary flag, violations,
|
|
10175
|
+
* and git metadata properties extracted from the scan result.
|
|
10176
|
+
*
|
|
10177
|
+
* @param {string} f Path to the Privado scan result JSON file
|
|
10178
|
+
* @returns {Object[]} List of service component objects suitable for a SaaSBOM
|
|
10179
|
+
*/
|
|
9760
10180
|
export function parsePrivadoFile(f) {
|
|
9761
10181
|
const pData = readFileSync(f, { encoding: "utf-8" });
|
|
9762
10182
|
const servlist = [];
|
|
@@ -9838,6 +10258,15 @@ export function parsePrivadoFile(f) {
|
|
|
9838
10258
|
return servlist;
|
|
9839
10259
|
}
|
|
9840
10260
|
|
|
10261
|
+
/**
|
|
10262
|
+
* Parses an OpenAPI specification (JSON or YAML string) and returns a list
|
|
10263
|
+
* containing a single service object with name, version, endpoints, and
|
|
10264
|
+
* authentication flag derived from the spec's info, servers, paths, and
|
|
10265
|
+
* securitySchemes sections.
|
|
10266
|
+
*
|
|
10267
|
+
* @param {string} oaData Raw JSON or YAML string contents of an OpenAPI specification
|
|
10268
|
+
* @returns {Object[]} List containing a single service component object
|
|
10269
|
+
*/
|
|
9841
10270
|
export function parseOpenapiSpecData(oaData) {
|
|
9842
10271
|
const servlist = [];
|
|
9843
10272
|
if (!oaData) {
|
|
@@ -9890,6 +10319,13 @@ export function parseOpenapiSpecData(oaData) {
|
|
|
9890
10319
|
return servlist;
|
|
9891
10320
|
}
|
|
9892
10321
|
|
|
10322
|
+
/**
|
|
10323
|
+
* Parses Haskell Cabal freeze file content and extracts package name and version
|
|
10324
|
+
* pairs from constraint lines (lines containing " ==").
|
|
10325
|
+
*
|
|
10326
|
+
* @param {string} cabalData Raw string contents of a Cabal freeze file
|
|
10327
|
+
* @returns {Object[]} List of package objects with name and version fields
|
|
10328
|
+
*/
|
|
9893
10329
|
export function parseCabalData(cabalData) {
|
|
9894
10330
|
const pkgList = [];
|
|
9895
10331
|
if (!cabalData) {
|
|
@@ -9918,6 +10354,13 @@ export function parseCabalData(cabalData) {
|
|
|
9918
10354
|
return pkgList;
|
|
9919
10355
|
}
|
|
9920
10356
|
|
|
10357
|
+
/**
|
|
10358
|
+
* Parses an Elixir mix.lock file and extracts Hex package name and version pairs
|
|
10359
|
+
* from lines containing ":hex".
|
|
10360
|
+
*
|
|
10361
|
+
* @param {string} mixData Raw string contents of a mix.lock file
|
|
10362
|
+
* @returns {Object[]} List of package objects with name and version fields
|
|
10363
|
+
*/
|
|
9921
10364
|
export function parseMixLockData(mixData) {
|
|
9922
10365
|
const pkgList = [];
|
|
9923
10366
|
if (!mixData) {
|
|
@@ -9945,404 +10388,55 @@ export function parseMixLockData(mixData) {
|
|
|
9945
10388
|
return pkgList;
|
|
9946
10389
|
}
|
|
9947
10390
|
|
|
10391
|
+
/**
|
|
10392
|
+
* Parses a GitHub Actions workflow YAML file and returns a list of action
|
|
10393
|
+
* components for each step that uses an external action (steps with a "uses"
|
|
10394
|
+
* field). Each component captures the action name, group, version/commit SHA,
|
|
10395
|
+
* version pinning type, job context (runner, permissions, environment), and
|
|
10396
|
+
* workflow-level metadata (triggers, concurrency, write permissions).
|
|
10397
|
+
*
|
|
10398
|
+
* @param {string} f Path to the GitHub Actions workflow YAML file
|
|
10399
|
+
* @returns {Object[]} List of action component objects with purl, properties, and evidence
|
|
10400
|
+
*/
|
|
9948
10401
|
export function parseGitHubWorkflowData(f) {
|
|
10402
|
+
const { components } = parseWorkflowFile(f);
|
|
10403
|
+
return components.filter((c) => c.scope === "required");
|
|
10404
|
+
}
|
|
10405
|
+
|
|
10406
|
+
/**
|
|
10407
|
+
* Parse Google Cloud Build YAML data and extract container image steps as packages.
|
|
10408
|
+
*
|
|
10409
|
+
* @param {string} cbwData Raw YAML string of a Cloud Build configuration file
|
|
10410
|
+
* @returns {Object[]} Array of package objects parsed from the build steps
|
|
10411
|
+
*/
|
|
10412
|
+
export function parseCloudBuildData(cbwData) {
|
|
9949
10413
|
const pkgList = [];
|
|
9950
|
-
if (!f) {
|
|
9951
|
-
return pkgList;
|
|
9952
|
-
}
|
|
9953
|
-
const ghwData = readFileSync(f, { encoding: "utf-8" });
|
|
9954
10414
|
const keys_cache = {};
|
|
9955
|
-
if (!
|
|
10415
|
+
if (!cbwData) {
|
|
9956
10416
|
return pkgList;
|
|
9957
10417
|
}
|
|
9958
|
-
const yamlObj = _load(
|
|
10418
|
+
const yamlObj = _load(cbwData);
|
|
9959
10419
|
if (!yamlObj) {
|
|
9960
10420
|
return pkgList;
|
|
9961
10421
|
}
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
workflowPermissions instanceof String) &&
|
|
9972
|
-
workflowPermissions.includes("write")
|
|
9973
|
-
) {
|
|
9974
|
-
hasWritePermissions = true;
|
|
9975
|
-
}
|
|
9976
|
-
const workflowConcurrency = yamlObj.concurrency || {};
|
|
9977
|
-
const _workflowEnv = yamlObj.env || {};
|
|
9978
|
-
const hasIdTokenWrite = workflowPermissions?.["id-token"] === "write";
|
|
9979
|
-
for (const jobName of Object.keys(yamlObj.jobs)) {
|
|
9980
|
-
const job = yamlObj.jobs[jobName];
|
|
9981
|
-
if (!job.steps) {
|
|
9982
|
-
continue;
|
|
9983
|
-
}
|
|
9984
|
-
// job-related values
|
|
9985
|
-
const jobRunner = job["runs-on"] || "unknown";
|
|
9986
|
-
const jobEnvironment = job.environment?.name || job.environment || "";
|
|
9987
|
-
const jobTimeout = job["timeout-minutes"] || null;
|
|
9988
|
-
const jobPermissions = job.permissions || {};
|
|
9989
|
-
const jobServices = job.services ? Object.keys(job.services) : [];
|
|
9990
|
-
let jobNeeds = job.needs || [];
|
|
9991
|
-
if (!Array.isArray(jobNeeds)) {
|
|
9992
|
-
jobNeeds = [jobNeeds];
|
|
9993
|
-
}
|
|
9994
|
-
const _jobIf = job.if || "";
|
|
9995
|
-
const _jobStrategy = job.strategy ? JSON.stringify(job.strategy) : "";
|
|
9996
|
-
const jobHasWritePermissions = analyzePermissions(jobPermissions);
|
|
9997
|
-
for (const step of job.steps) {
|
|
9998
|
-
if (step.uses) {
|
|
9999
|
-
const tmpA = step.uses.split("@");
|
|
10000
|
-
if (tmpA.length !== 2) {
|
|
10001
|
-
continue;
|
|
10002
|
-
}
|
|
10003
|
-
const groupName = tmpA[0];
|
|
10004
|
-
let name = groupName;
|
|
10005
|
-
let group = "";
|
|
10006
|
-
const tagOrCommit = tmpA[1];
|
|
10007
|
-
let version = tagOrCommit;
|
|
10008
|
-
const tmpB = groupName.split("/");
|
|
10009
|
-
if (tmpB.length >= 2) {
|
|
10010
|
-
name = tmpB.pop();
|
|
10011
|
-
group = tmpB.join("/");
|
|
10012
|
-
} else if (tmpB.length === 1) {
|
|
10013
|
-
name = tmpB[0];
|
|
10014
|
-
group = "";
|
|
10015
|
-
}
|
|
10016
|
-
const versionPinningType = getVersionPinningType(tagOrCommit);
|
|
10017
|
-
const isShaPinned = versionPinningType === "sha";
|
|
10018
|
-
const _isTagPinned = versionPinningType === "tag";
|
|
10019
|
-
const _isBranchRef = versionPinningType === "branch";
|
|
10020
|
-
let lineNum = -1;
|
|
10021
|
-
const stepLineMatch = ghwData.indexOf(step.uses);
|
|
10022
|
-
if (stepLineMatch >= 0) {
|
|
10023
|
-
lineNum = ghwData.substring(0, stepLineMatch).split("\n").length - 1;
|
|
10024
|
-
}
|
|
10025
|
-
if (lineNum >= 0 && lines[lineNum]) {
|
|
10026
|
-
const line = lines[lineNum];
|
|
10027
|
-
const commentMatch = line.match(/#\s*v?([0-9]+(?:\.[0-9]+)*)/);
|
|
10028
|
-
if (commentMatch?.[1]) {
|
|
10029
|
-
version = commentMatch[1];
|
|
10030
|
-
}
|
|
10031
|
-
}
|
|
10032
|
-
const key = `${group}-${name}-${version}`;
|
|
10033
|
-
let confidence = 0.6;
|
|
10034
|
-
if (!keys_cache[key] && name && version) {
|
|
10035
|
-
keys_cache[key] = key;
|
|
10036
|
-
let fullName = name;
|
|
10037
|
-
if (group.length) {
|
|
10038
|
-
fullName = `${group}/${name}`;
|
|
10039
|
-
}
|
|
10040
|
-
let purl = `pkg:github/${fullName}@${version}`;
|
|
10041
|
-
if (tagOrCommit && version !== tagOrCommit) {
|
|
10042
|
-
const qualifierDesc = tagOrCommit.startsWith("v")
|
|
10043
|
-
? "tag"
|
|
10044
|
-
: "commit";
|
|
10045
|
-
purl = `${purl}?${qualifierDesc}=${tagOrCommit}`;
|
|
10046
|
-
confidence = 0.7;
|
|
10047
|
-
}
|
|
10048
|
-
const properties = [
|
|
10049
|
-
{ name: "SrcFile", value: f },
|
|
10050
|
-
{ name: "cdx:github:workflow:name", value: workflowName },
|
|
10051
|
-
{ name: "cdx:github:job:name", value: jobName },
|
|
10052
|
-
{
|
|
10053
|
-
name: "cdx:github:job:runner",
|
|
10054
|
-
value: Array.isArray(jobRunner) ? jobRunner.join(",") : jobRunner,
|
|
10055
|
-
},
|
|
10056
|
-
{ name: "cdx:github:action:uses", value: step.uses },
|
|
10057
|
-
{
|
|
10058
|
-
name: "cdx:github:action:versionPinningType",
|
|
10059
|
-
value: versionPinningType,
|
|
10060
|
-
},
|
|
10061
|
-
{
|
|
10062
|
-
name: "cdx:github:action:isShaPinned",
|
|
10063
|
-
value: isShaPinned.toString(),
|
|
10064
|
-
},
|
|
10065
|
-
];
|
|
10066
|
-
if (step.name) {
|
|
10067
|
-
properties.push({ name: "cdx:github:step:name", value: step.name });
|
|
10068
|
-
}
|
|
10069
|
-
if (step.if) {
|
|
10070
|
-
properties.push({
|
|
10071
|
-
name: "cdx:github:step:condition",
|
|
10072
|
-
value: step.if,
|
|
10073
|
-
});
|
|
10074
|
-
}
|
|
10075
|
-
if (step["continue-on-error"]) {
|
|
10076
|
-
properties.push({
|
|
10077
|
-
name: "cdx:github:step:continueOnError",
|
|
10078
|
-
value: "true",
|
|
10079
|
-
});
|
|
10422
|
+
if (yamlObj.steps) {
|
|
10423
|
+
for (const step of yamlObj.steps) {
|
|
10424
|
+
if (step.name) {
|
|
10425
|
+
const tmpA = step.name.split(":");
|
|
10426
|
+
if (tmpA.length === 2) {
|
|
10427
|
+
let group = dirname(tmpA[0]);
|
|
10428
|
+
const name = basename(tmpA[0]);
|
|
10429
|
+
if (group === ".") {
|
|
10430
|
+
group = "";
|
|
10080
10431
|
}
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
name: "cdx:github:job:environment",
|
|
10090
|
-
value: jobEnvironment,
|
|
10091
|
-
});
|
|
10092
|
-
}
|
|
10093
|
-
if (jobTimeout) {
|
|
10094
|
-
properties.push({
|
|
10095
|
-
name: "cdx:github:job:timeoutMinutes",
|
|
10096
|
-
value: jobTimeout.toString(),
|
|
10097
|
-
});
|
|
10098
|
-
}
|
|
10099
|
-
if (jobHasWritePermissions) {
|
|
10100
|
-
properties.push({
|
|
10101
|
-
name: "cdx:github:job:hasWritePermissions",
|
|
10102
|
-
value: "true",
|
|
10103
|
-
});
|
|
10104
|
-
}
|
|
10105
|
-
if (jobServices.length > 0) {
|
|
10106
|
-
properties.push({
|
|
10107
|
-
name: "cdx:github:job:services",
|
|
10108
|
-
value: jobServices.join(","),
|
|
10109
|
-
});
|
|
10110
|
-
}
|
|
10111
|
-
if (jobNeeds.length > 0) {
|
|
10112
|
-
properties.push({
|
|
10113
|
-
name: "cdx:github:job:needs",
|
|
10114
|
-
value: jobNeeds.join(","),
|
|
10115
|
-
});
|
|
10116
|
-
}
|
|
10117
|
-
if (hasWritePermissions) {
|
|
10118
|
-
properties.push({
|
|
10119
|
-
name: "cdx:github:workflow:hasWritePermissions",
|
|
10120
|
-
value: "true",
|
|
10121
|
-
});
|
|
10122
|
-
}
|
|
10123
|
-
if (hasIdTokenWrite) {
|
|
10124
|
-
properties.push({
|
|
10125
|
-
name: "cdx:github:workflow:hasIdTokenWrite",
|
|
10126
|
-
value: "true",
|
|
10127
|
-
});
|
|
10128
|
-
}
|
|
10129
|
-
if (workflowConcurrency?.group) {
|
|
10130
|
-
properties.push({
|
|
10131
|
-
name: "cdx:github:workflow:concurrencyGroup",
|
|
10132
|
-
value: workflowConcurrency.group,
|
|
10133
|
-
});
|
|
10134
|
-
}
|
|
10135
|
-
if (group?.startsWith("github/") || group === "actions") {
|
|
10136
|
-
properties.push({ name: "cdx:actions:isOfficial", value: "true" });
|
|
10137
|
-
}
|
|
10138
|
-
if (group?.startsWith("github/")) {
|
|
10139
|
-
properties.push({ name: "cdx:actions:isVerified", value: "true" });
|
|
10140
|
-
}
|
|
10141
|
-
if (workflowTriggers) {
|
|
10142
|
-
const triggers =
|
|
10143
|
-
typeof workflowTriggers === "string"
|
|
10144
|
-
? workflowTriggers
|
|
10145
|
-
: Object.keys(workflowTriggers).join(",");
|
|
10146
|
-
properties.push({
|
|
10147
|
-
name: "cdx:github:workflow:triggers",
|
|
10148
|
-
value: triggers,
|
|
10149
|
-
});
|
|
10150
|
-
}
|
|
10151
|
-
pkgList.push({
|
|
10152
|
-
group,
|
|
10153
|
-
name,
|
|
10154
|
-
version,
|
|
10155
|
-
purl,
|
|
10156
|
-
properties,
|
|
10157
|
-
evidence: {
|
|
10158
|
-
identity: {
|
|
10159
|
-
field: "purl",
|
|
10160
|
-
confidence,
|
|
10161
|
-
methods: [
|
|
10162
|
-
{
|
|
10163
|
-
technique: "source-code-analysis",
|
|
10164
|
-
confidence,
|
|
10165
|
-
value: f,
|
|
10166
|
-
},
|
|
10167
|
-
],
|
|
10168
|
-
},
|
|
10169
|
-
},
|
|
10170
|
-
});
|
|
10171
|
-
}
|
|
10172
|
-
}
|
|
10173
|
-
if (step.run) {
|
|
10174
|
-
const runLineNum = ghwData.indexOf(step.run);
|
|
10175
|
-
const runLine =
|
|
10176
|
-
runLineNum >= 0
|
|
10177
|
-
? ghwData.substring(0, runLineNum).split("\n").length
|
|
10178
|
-
: -1;
|
|
10179
|
-
const pkgCommands = extractPackageManagerCommands(step.run);
|
|
10180
|
-
for (const pkgCmd of pkgCommands) {
|
|
10181
|
-
const key = `run-${pkgCmd.name}-${pkgCmd.version || "latest"}`;
|
|
10182
|
-
if (!keys_cache[key]) {
|
|
10183
|
-
keys_cache[key] = key;
|
|
10184
|
-
pkgList.push({
|
|
10185
|
-
group: "",
|
|
10186
|
-
name: pkgCmd.name,
|
|
10187
|
-
version: pkgCmd.version || undefined,
|
|
10188
|
-
scope: "excluded",
|
|
10189
|
-
"bom-ref": uuidv4(),
|
|
10190
|
-
description: key,
|
|
10191
|
-
properties: [
|
|
10192
|
-
{ name: "SrcFile", value: f },
|
|
10193
|
-
{ name: "cdx:github:workflow:name", value: workflowName },
|
|
10194
|
-
{ name: "cdx:github:job:name", value: jobName },
|
|
10195
|
-
{ name: "cdx:github:step:type", value: "run" },
|
|
10196
|
-
{ name: "cdx:github:step:command", value: pkgCmd.command },
|
|
10197
|
-
{ name: "cdx:github:run:line", value: runLine.toString() },
|
|
10198
|
-
],
|
|
10199
|
-
evidence: {
|
|
10200
|
-
identity: {
|
|
10201
|
-
field: "purl",
|
|
10202
|
-
confidence: 0.5,
|
|
10203
|
-
methods: [
|
|
10204
|
-
{
|
|
10205
|
-
technique: "source-code-analysis",
|
|
10206
|
-
confidence: 0.5,
|
|
10207
|
-
value: f,
|
|
10208
|
-
},
|
|
10209
|
-
],
|
|
10210
|
-
},
|
|
10211
|
-
},
|
|
10212
|
-
});
|
|
10213
|
-
}
|
|
10214
|
-
}
|
|
10215
|
-
}
|
|
10216
|
-
}
|
|
10217
|
-
}
|
|
10218
|
-
return pkgList;
|
|
10219
|
-
}
|
|
10220
|
-
|
|
10221
|
-
/**
|
|
10222
|
-
* Analyze permissions for write access.
|
|
10223
|
-
*
|
|
10224
|
-
* Refer to https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#permissions
|
|
10225
|
-
*/
|
|
10226
|
-
function analyzePermissions(permissions) {
|
|
10227
|
-
if (!permissions || typeof permissions !== "object") {
|
|
10228
|
-
return false;
|
|
10229
|
-
}
|
|
10230
|
-
const writePermissions = [
|
|
10231
|
-
"actions",
|
|
10232
|
-
"artifact-metadata",
|
|
10233
|
-
"attestations",
|
|
10234
|
-
"checks",
|
|
10235
|
-
"contents",
|
|
10236
|
-
"deployments",
|
|
10237
|
-
"id-token",
|
|
10238
|
-
"models",
|
|
10239
|
-
"discussions",
|
|
10240
|
-
"packages",
|
|
10241
|
-
"pages",
|
|
10242
|
-
"actions",
|
|
10243
|
-
"deployments",
|
|
10244
|
-
"issues",
|
|
10245
|
-
"pull-requests",
|
|
10246
|
-
"security-events",
|
|
10247
|
-
"statuses",
|
|
10248
|
-
];
|
|
10249
|
-
for (const perm of writePermissions) {
|
|
10250
|
-
if (permissions[perm] === "write") {
|
|
10251
|
-
return true;
|
|
10252
|
-
}
|
|
10253
|
-
}
|
|
10254
|
-
return false;
|
|
10255
|
-
}
|
|
10256
|
-
|
|
10257
|
-
/**
|
|
10258
|
-
* Determine version pinning type for security assessment
|
|
10259
|
-
*/
|
|
10260
|
-
function getVersionPinningType(versionRef) {
|
|
10261
|
-
if (!versionRef) {
|
|
10262
|
-
return "unknown";
|
|
10263
|
-
}
|
|
10264
|
-
if (/^[a-f0-9]{40}$/.test(versionRef)) {
|
|
10265
|
-
return "sha";
|
|
10266
|
-
}
|
|
10267
|
-
if (/^[a-f0-9]{7,}$/.test(versionRef)) {
|
|
10268
|
-
return "sha";
|
|
10269
|
-
}
|
|
10270
|
-
if (
|
|
10271
|
-
versionRef === "main" ||
|
|
10272
|
-
versionRef === "master" ||
|
|
10273
|
-
versionRef.includes("/")
|
|
10274
|
-
) {
|
|
10275
|
-
return "branch";
|
|
10276
|
-
}
|
|
10277
|
-
return "tag";
|
|
10278
|
-
}
|
|
10279
|
-
|
|
10280
|
-
/**
|
|
10281
|
-
* Extract package manager commands from run steps
|
|
10282
|
-
*/
|
|
10283
|
-
function extractPackageManagerCommands(runScript) {
|
|
10284
|
-
const commands = [];
|
|
10285
|
-
if (!runScript) {
|
|
10286
|
-
return commands;
|
|
10287
|
-
}
|
|
10288
|
-
const patterns = [
|
|
10289
|
-
{ regex: /npm\s+(install|i|ci)\s+([^&\n|;]+)/g, name: "npm", type: "npm" },
|
|
10290
|
-
{
|
|
10291
|
-
regex: /yarn\s+(add|install)\s+([^&\n|;]+)/g,
|
|
10292
|
-
name: "yarn",
|
|
10293
|
-
type: "yarn",
|
|
10294
|
-
},
|
|
10295
|
-
{ regex: /pip\s+(install)\s+([^&\n|;]+)/g, name: "pip", type: "pip" },
|
|
10296
|
-
{ regex: /pip3\s+(install)\s+([^&\n|;]+)/g, name: "pip3", type: "pip" },
|
|
10297
|
-
{ regex: /gem\s+(install)\s+([^&\n|;]+)/g, name: "gem", type: "gem" },
|
|
10298
|
-
{ regex: /go\s+(get|install)\s+([^&\n|;]+)/g, name: "go", type: "go" },
|
|
10299
|
-
{
|
|
10300
|
-
regex: /cargo\s+(install|add)\s+([^&\n|;]+)/g,
|
|
10301
|
-
name: "cargo",
|
|
10302
|
-
type: "cargo",
|
|
10303
|
-
},
|
|
10304
|
-
];
|
|
10305
|
-
for (const pattern of patterns) {
|
|
10306
|
-
let match;
|
|
10307
|
-
while ((match = pattern.regex.exec(runScript)) !== null) {
|
|
10308
|
-
commands.push({
|
|
10309
|
-
name: pattern.name,
|
|
10310
|
-
command: match[0],
|
|
10311
|
-
version: null,
|
|
10312
|
-
});
|
|
10313
|
-
}
|
|
10314
|
-
}
|
|
10315
|
-
return commands;
|
|
10316
|
-
}
|
|
10317
|
-
|
|
10318
|
-
export function parseCloudBuildData(cbwData) {
|
|
10319
|
-
const pkgList = [];
|
|
10320
|
-
const keys_cache = {};
|
|
10321
|
-
if (!cbwData) {
|
|
10322
|
-
return pkgList;
|
|
10323
|
-
}
|
|
10324
|
-
const yamlObj = _load(cbwData);
|
|
10325
|
-
if (!yamlObj) {
|
|
10326
|
-
return pkgList;
|
|
10327
|
-
}
|
|
10328
|
-
if (yamlObj.steps) {
|
|
10329
|
-
for (const step of yamlObj.steps) {
|
|
10330
|
-
if (step.name) {
|
|
10331
|
-
const tmpA = step.name.split(":");
|
|
10332
|
-
if (tmpA.length === 2) {
|
|
10333
|
-
let group = dirname(tmpA[0]);
|
|
10334
|
-
const name = basename(tmpA[0]);
|
|
10335
|
-
if (group === ".") {
|
|
10336
|
-
group = "";
|
|
10337
|
-
}
|
|
10338
|
-
const version = tmpA[1];
|
|
10339
|
-
const key = `${group}-${name}-${version}`;
|
|
10340
|
-
if (!keys_cache[key] && name && version) {
|
|
10341
|
-
keys_cache[key] = key;
|
|
10342
|
-
pkgList.push({
|
|
10343
|
-
group,
|
|
10344
|
-
name,
|
|
10345
|
-
version,
|
|
10432
|
+
const version = tmpA[1];
|
|
10433
|
+
const key = `${group}-${name}-${version}`;
|
|
10434
|
+
if (!keys_cache[key] && name && version) {
|
|
10435
|
+
keys_cache[key] = key;
|
|
10436
|
+
pkgList.push({
|
|
10437
|
+
group,
|
|
10438
|
+
name,
|
|
10439
|
+
version,
|
|
10346
10440
|
});
|
|
10347
10441
|
}
|
|
10348
10442
|
}
|
|
@@ -10391,6 +10485,16 @@ function untilFirst(separator, inputStr) {
|
|
|
10391
10485
|
];
|
|
10392
10486
|
}
|
|
10393
10487
|
|
|
10488
|
+
/**
|
|
10489
|
+
* Map a Conan package reference string to a PackageURL string, name, and version.
|
|
10490
|
+
*
|
|
10491
|
+
* Parses a full Conan package reference of the form
|
|
10492
|
+
* `name/version@user/channel#recipe_revision:package_id#package_revision`
|
|
10493
|
+
* and returns the equivalent purl string together with the extracted name and version.
|
|
10494
|
+
*
|
|
10495
|
+
* @param {string} conanPkgRef Conan package reference string
|
|
10496
|
+
* @returns {Array} Tuple of [purlString, name, version], or [null, null, null] on parse failure
|
|
10497
|
+
*/
|
|
10394
10498
|
export function mapConanPkgRefToPurlStringAndNameAndVersion(conanPkgRef) {
|
|
10395
10499
|
// A full Conan package reference may be composed of the following segments:
|
|
10396
10500
|
// conanPkgRef = "name/version@user/channel#recipe_revision:package_id#package_revision"
|
|
@@ -10516,6 +10620,16 @@ export function mapConanPkgRefToPurlStringAndNameAndVersion(conanPkgRef) {
|
|
|
10516
10620
|
return [purl, info.name, info.version];
|
|
10517
10621
|
}
|
|
10518
10622
|
|
|
10623
|
+
/**
|
|
10624
|
+
* Parse Conan lock file data (conan.lock) and return the package list, dependency map,
|
|
10625
|
+
* and parent component dependencies.
|
|
10626
|
+
*
|
|
10627
|
+
* Supports both the legacy `graph_lock.nodes` format (Conan 1.x) and the newer
|
|
10628
|
+
* `requires` format (Conan 2.x).
|
|
10629
|
+
*
|
|
10630
|
+
* @param {string} conanLockData Raw JSON string of the Conan lock file
|
|
10631
|
+
* @returns {{ pkgList: Object[], dependencies: Object, parentComponentDependencies: string[] }}
|
|
10632
|
+
*/
|
|
10519
10633
|
export function parseConanLockData(conanLockData) {
|
|
10520
10634
|
const pkgList = [];
|
|
10521
10635
|
const dependencies = {};
|
|
@@ -10526,10 +10640,7 @@ export function parseConanLockData(conanLockData) {
|
|
|
10526
10640
|
}
|
|
10527
10641
|
|
|
10528
10642
|
const lockFile = JSON.parse(conanLockData);
|
|
10529
|
-
if (
|
|
10530
|
-
(!lockFile || !lockFile.graph_lock || !lockFile.graph_lock.nodes) &&
|
|
10531
|
-
!lockFile.requires
|
|
10532
|
-
) {
|
|
10643
|
+
if (!lockFile?.graph_lock?.nodes && !lockFile.requires) {
|
|
10533
10644
|
return { pkgList, dependencies, parentComponentDependencies };
|
|
10534
10645
|
}
|
|
10535
10646
|
|
|
@@ -10602,6 +10713,12 @@ export function parseConanLockData(conanLockData) {
|
|
|
10602
10713
|
return { pkgList, dependencies, parentComponentDependencies };
|
|
10603
10714
|
}
|
|
10604
10715
|
|
|
10716
|
+
/**
|
|
10717
|
+
* Parse a Conan conanfile.txt and extract required and optional packages.
|
|
10718
|
+
*
|
|
10719
|
+
* @param {string} conanData Raw text contents of a conanfile.txt
|
|
10720
|
+
* @returns {Object[]} Array of package objects with purl, name, version, and scope
|
|
10721
|
+
*/
|
|
10605
10722
|
export function parseConanData(conanData) {
|
|
10606
10723
|
const pkgList = [];
|
|
10607
10724
|
if (!conanData) {
|
|
@@ -10637,6 +10754,12 @@ export function parseConanData(conanData) {
|
|
|
10637
10754
|
return pkgList;
|
|
10638
10755
|
}
|
|
10639
10756
|
|
|
10757
|
+
/**
|
|
10758
|
+
* Parse Leiningen project.clj data and extract dependency packages.
|
|
10759
|
+
*
|
|
10760
|
+
* @param {string} leinData Raw text contents of a Leiningen project.clj file
|
|
10761
|
+
* @returns {Object[]} Array of package objects with group, name, and version
|
|
10762
|
+
*/
|
|
10640
10763
|
export function parseLeiningenData(leinData) {
|
|
10641
10764
|
const pkgList = [];
|
|
10642
10765
|
if (!leinData) {
|
|
@@ -10671,6 +10794,14 @@ export function parseLeiningenData(leinData) {
|
|
|
10671
10794
|
return pkgList;
|
|
10672
10795
|
}
|
|
10673
10796
|
|
|
10797
|
+
/**
|
|
10798
|
+
* Parse EDN (Extensible Data Notation) deps.edn data and extract dependency packages.
|
|
10799
|
+
*
|
|
10800
|
+
* Handles Clojure deps.edn files, extracting packages listed under the `:deps` key.
|
|
10801
|
+
*
|
|
10802
|
+
* @param {string} rawEdnData Raw EDN text contents of a deps.edn file
|
|
10803
|
+
* @returns {Object[]} Array of package objects with group, name, and version
|
|
10804
|
+
*/
|
|
10674
10805
|
export function parseEdnData(rawEdnData) {
|
|
10675
10806
|
const pkgList = [];
|
|
10676
10807
|
if (!rawEdnData) {
|
|
@@ -10750,7 +10881,7 @@ export function parseFlakeNix(flakeNixFile) {
|
|
|
10750
10881
|
const pkgList = [];
|
|
10751
10882
|
const dependencies = [];
|
|
10752
10883
|
|
|
10753
|
-
if (!
|
|
10884
|
+
if (!safeExistsSync(flakeNixFile)) {
|
|
10754
10885
|
return { pkgList, dependencies };
|
|
10755
10886
|
}
|
|
10756
10887
|
|
|
@@ -10835,7 +10966,7 @@ export function parseFlakeLock(flakeLockFile) {
|
|
|
10835
10966
|
const pkgList = [];
|
|
10836
10967
|
const dependencies = [];
|
|
10837
10968
|
|
|
10838
|
-
if (!
|
|
10969
|
+
if (!safeExistsSync(flakeLockFile)) {
|
|
10839
10970
|
return { pkgList, dependencies };
|
|
10840
10971
|
}
|
|
10841
10972
|
|
|
@@ -11117,6 +11248,13 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
11117
11248
|
};
|
|
11118
11249
|
}
|
|
11119
11250
|
|
|
11251
|
+
/**
|
|
11252
|
+
* Parse a C# packages.config XML file and return a list of NuGet package components.
|
|
11253
|
+
*
|
|
11254
|
+
* @param {string} pkgData Raw XML string of a packages.config file
|
|
11255
|
+
* @param {string} pkgFile Path to the packages.config file, used for evidence properties
|
|
11256
|
+
* @returns {Object[]} Array of NuGet package objects with purl, name, and version
|
|
11257
|
+
*/
|
|
11120
11258
|
export function parseCsPkgData(pkgData, pkgFile) {
|
|
11121
11259
|
const pkgList = [];
|
|
11122
11260
|
if (!pkgData) {
|
|
@@ -11649,6 +11787,17 @@ export function parseCsProjData(
|
|
|
11649
11787
|
};
|
|
11650
11788
|
}
|
|
11651
11789
|
|
|
11790
|
+
/**
|
|
11791
|
+
* Parse a .NET project.assets.json file and return the package list and dependency tree.
|
|
11792
|
+
*
|
|
11793
|
+
* Extracts NuGet packages and their transitive dependency relationships from the
|
|
11794
|
+
* `libraries` and `targets` sections of a project.assets.json file produced by
|
|
11795
|
+
* the .NET restore process.
|
|
11796
|
+
*
|
|
11797
|
+
* @param {string} csProjData Raw JSON string of the project.assets.json file
|
|
11798
|
+
* @param {string} assetsJsonFile Path to the project.assets.json file, used for evidence properties
|
|
11799
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
11800
|
+
*/
|
|
11652
11801
|
export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
11653
11802
|
// extract name, operator, version from .NET package representation
|
|
11654
11803
|
// like "NLog >= 4.5.0"
|
|
@@ -11899,6 +12048,14 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
11899
12048
|
};
|
|
11900
12049
|
}
|
|
11901
12050
|
|
|
12051
|
+
/**
|
|
12052
|
+
* Parse a .NET packages.lock.json file and return the package list, dependency tree,
|
|
12053
|
+
* and list of direct/root dependencies.
|
|
12054
|
+
*
|
|
12055
|
+
* @param {string} csLockData Raw JSON string of the packages.lock.json file
|
|
12056
|
+
* @param {string} pkgLockFile Path to the packages.lock.json file, used for evidence properties
|
|
12057
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[], rootList: Object[] }}
|
|
12058
|
+
*/
|
|
11902
12059
|
export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
11903
12060
|
const pkgList = [];
|
|
11904
12061
|
const dependenciesList = [];
|
|
@@ -11912,7 +12069,7 @@ export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
|
11912
12069
|
};
|
|
11913
12070
|
}
|
|
11914
12071
|
const assetData = JSON.parse(csLockData);
|
|
11915
|
-
if (!assetData
|
|
12072
|
+
if (!assetData?.dependencies) {
|
|
11916
12073
|
return {
|
|
11917
12074
|
pkgList,
|
|
11918
12075
|
dependenciesList,
|
|
@@ -12017,6 +12174,14 @@ export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
|
12017
12174
|
};
|
|
12018
12175
|
}
|
|
12019
12176
|
|
|
12177
|
+
/**
|
|
12178
|
+
* Parse a Paket dependency manager lock file (paket.lock) and return the package list
|
|
12179
|
+
* and dependency tree.
|
|
12180
|
+
*
|
|
12181
|
+
* @param {string} paketLockData Raw text contents of the paket.lock file
|
|
12182
|
+
* @param {string} pkgLockFile Path to the paket.lock file, used for evidence properties
|
|
12183
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
12184
|
+
*/
|
|
12020
12185
|
export function parsePaketLockData(paketLockData, pkgLockFile) {
|
|
12021
12186
|
const pkgList = [];
|
|
12022
12187
|
const dependenciesList = [];
|
|
@@ -12191,6 +12356,16 @@ export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
|
12191
12356
|
const rootRequiresMap = {};
|
|
12192
12357
|
if (rootRequires) {
|
|
12193
12358
|
for (const rr of Object.keys(rootRequires)) {
|
|
12359
|
+
// Skip platform requirements (php, hhvm, ext-*, lib-*) — they are never
|
|
12360
|
+
// Composer package names and must not be used to identify root packages.
|
|
12361
|
+
if (
|
|
12362
|
+
rr === "php" ||
|
|
12363
|
+
rr === "hhvm" ||
|
|
12364
|
+
rr.startsWith("ext-") ||
|
|
12365
|
+
rr.startsWith("lib-")
|
|
12366
|
+
) {
|
|
12367
|
+
continue;
|
|
12368
|
+
}
|
|
12194
12369
|
rootRequiresMap[rr] = true;
|
|
12195
12370
|
}
|
|
12196
12371
|
}
|
|
@@ -12215,7 +12390,7 @@ export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
|
12215
12390
|
for (const i in packages[compScope]) {
|
|
12216
12391
|
const pkg = packages[compScope][i];
|
|
12217
12392
|
// Be extra cautious. Potential fix for #236
|
|
12218
|
-
if (!pkg
|
|
12393
|
+
if (!pkg?.name || !pkg.version) {
|
|
12219
12394
|
continue;
|
|
12220
12395
|
}
|
|
12221
12396
|
|
|
@@ -12313,7 +12488,7 @@ export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
|
12313
12488
|
for (const compScope in packages) {
|
|
12314
12489
|
for (const i in packages[compScope]) {
|
|
12315
12490
|
const pkg = packages[compScope][i];
|
|
12316
|
-
if (!pkg
|
|
12491
|
+
if (!pkg?.name || !pkg.version) {
|
|
12317
12492
|
continue;
|
|
12318
12493
|
}
|
|
12319
12494
|
if (!pkg.require || !Object.keys(pkg.require).length) {
|
|
@@ -12342,6 +12517,15 @@ export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
|
12342
12517
|
};
|
|
12343
12518
|
}
|
|
12344
12519
|
|
|
12520
|
+
/**
|
|
12521
|
+
* Parse an sbt dependency tree output file and return the package list and dependency tree.
|
|
12522
|
+
*
|
|
12523
|
+
* Reads a file produced by the sbt `dependencyTree` command and extracts Maven artifact
|
|
12524
|
+
* coordinates, building a hierarchical dependency graph. Evicted packages and ranges are ignored.
|
|
12525
|
+
*
|
|
12526
|
+
* @param {string} sbtTreeFile Path to the sbt dependency tree output file
|
|
12527
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
12528
|
+
*/
|
|
12345
12529
|
export function parseSbtTree(sbtTreeFile) {
|
|
12346
12530
|
const pkgList = [];
|
|
12347
12531
|
const dependenciesList = [];
|
|
@@ -12658,6 +12842,10 @@ export function convertOSQueryResults(
|
|
|
12658
12842
|
if (publisher === "null") {
|
|
12659
12843
|
publisher = "";
|
|
12660
12844
|
}
|
|
12845
|
+
// For vscode-extension purl type, the publisher is used as the namespace
|
|
12846
|
+
if (queryObj.purlType === "vscode-extension" && publisher) {
|
|
12847
|
+
group = publisher.toLowerCase();
|
|
12848
|
+
}
|
|
12661
12849
|
let scope;
|
|
12662
12850
|
const compScope = res.priority;
|
|
12663
12851
|
if (["required", "optional", "excluded"].includes(compScope)) {
|
|
@@ -12758,6 +12946,17 @@ export function convertOSQueryResults(
|
|
|
12758
12946
|
return pkgList;
|
|
12759
12947
|
}
|
|
12760
12948
|
|
|
12949
|
+
/**
|
|
12950
|
+
* Create a PackageURL object from a repository URL string, package type, and version.
|
|
12951
|
+
*
|
|
12952
|
+
* Supports HTTPS URLs, SSH `git@` URLs, Bitbucket SSH URLs, and local paths.
|
|
12953
|
+
* Extracts the namespace (host + path prefix) and repository name from the URL.
|
|
12954
|
+
*
|
|
12955
|
+
* @param {string} type PackageURL type (e.g. `"swift"`, `"generic"`)
|
|
12956
|
+
* @param {string} repoUrl Repository URL string
|
|
12957
|
+
* @param {string} version Package version
|
|
12958
|
+
* @returns {PackageURL|undefined} PackageURL object, or undefined for unsupported URL formats
|
|
12959
|
+
*/
|
|
12761
12960
|
export function purlFromUrlString(type, repoUrl, version) {
|
|
12762
12961
|
let namespace = "";
|
|
12763
12962
|
let name;
|
|
@@ -13050,6 +13249,20 @@ export async function collectMvnDependencies(
|
|
|
13050
13249
|
return jarNSMapping;
|
|
13051
13250
|
}
|
|
13052
13251
|
|
|
13252
|
+
/**
|
|
13253
|
+
* Collect Gradle project dependencies by scanning the Gradle cache directory for JAR files
|
|
13254
|
+
* and their associated POM files.
|
|
13255
|
+
*
|
|
13256
|
+
* Uses the `GRADLE_CACHE_DIR` or `GRADLE_USER_HOME` environment variables to locate the
|
|
13257
|
+
* Gradle files-2.1 cache, then delegates to {@link collectJarNS} to extract namespace
|
|
13258
|
+
* and purl information from those JARs.
|
|
13259
|
+
*
|
|
13260
|
+
* @param {string} _gradleCmd Gradle command (unused; reserved for future use)
|
|
13261
|
+
* @param {string} _basePath Base project path (unused; reserved for future use)
|
|
13262
|
+
* @param {boolean} _cleanup Whether to clean up temporary files (unused; reserved for future use)
|
|
13263
|
+
* @param {boolean} _includeCacheDir Whether to include cache directory (unused; reserved for future use)
|
|
13264
|
+
* @returns {Promise<Object>} JAR namespace mapping object returned by collectJarNS
|
|
13265
|
+
*/
|
|
13053
13266
|
export async function collectGradleDependencies(
|
|
13054
13267
|
_gradleCmd,
|
|
13055
13268
|
_basePath,
|
|
@@ -13263,6 +13476,16 @@ export async function collectJarNS(jarPath, pomPathMap = {}) {
|
|
|
13263
13476
|
return jarNSMapping;
|
|
13264
13477
|
}
|
|
13265
13478
|
|
|
13479
|
+
/**
|
|
13480
|
+
* Convert a JAR namespace mapping (produced by {@link collectJarNS}) into an array
|
|
13481
|
+
* of CycloneDX package component objects.
|
|
13482
|
+
*
|
|
13483
|
+
* Each entry in the mapping is resolved to a component with name, group, version,
|
|
13484
|
+
* purl, hashes, namespace properties, and source file evidence.
|
|
13485
|
+
*
|
|
13486
|
+
* @param {Object} jarNSMapping Map of purl string to `{ jarFile, pom, namespaces, hashes }`
|
|
13487
|
+
* @returns {Promise<Object[]>} Array of component objects derived from the JAR mapping
|
|
13488
|
+
*/
|
|
13266
13489
|
export async function convertJarNSToPackages(jarNSMapping) {
|
|
13267
13490
|
const pkgList = [];
|
|
13268
13491
|
for (const purl of Object.keys(jarNSMapping)) {
|
|
@@ -13366,6 +13589,12 @@ export function parsePomXml(pomXmlData) {
|
|
|
13366
13589
|
return undefined;
|
|
13367
13590
|
}
|
|
13368
13591
|
|
|
13592
|
+
/**
|
|
13593
|
+
* Parse a JAR MANIFEST.MF file and return its key-value pairs as an object.
|
|
13594
|
+
*
|
|
13595
|
+
* @param {string} jarMetadata Raw text contents of a MANIFEST.MF file
|
|
13596
|
+
* @returns {Object} Key-value pairs extracted from the manifest
|
|
13597
|
+
*/
|
|
13369
13598
|
export function parseJarManifest(jarMetadata) {
|
|
13370
13599
|
const metadata = {};
|
|
13371
13600
|
if (!jarMetadata) {
|
|
@@ -13383,6 +13612,12 @@ export function parseJarManifest(jarMetadata) {
|
|
|
13383
13612
|
return metadata;
|
|
13384
13613
|
}
|
|
13385
13614
|
|
|
13615
|
+
/**
|
|
13616
|
+
* Parse a Maven pom.properties file and return its key-value pairs as an object.
|
|
13617
|
+
*
|
|
13618
|
+
* @param {string} pomProperties Raw text contents of a pom.properties file
|
|
13619
|
+
* @returns {Object} Key-value pairs extracted from the properties file
|
|
13620
|
+
*/
|
|
13386
13621
|
export function parsePomProperties(pomProperties) {
|
|
13387
13622
|
const properties = {};
|
|
13388
13623
|
if (!pomProperties) {
|
|
@@ -13400,6 +13635,13 @@ export function parsePomProperties(pomProperties) {
|
|
|
13400
13635
|
return properties;
|
|
13401
13636
|
}
|
|
13402
13637
|
|
|
13638
|
+
/**
|
|
13639
|
+
* Encode a string for safe inclusion in a PackageURL, percent-encoding special characters
|
|
13640
|
+
* while preserving already-encoded `%40` sequences and keeping `:` and `/` unencoded.
|
|
13641
|
+
*
|
|
13642
|
+
* @param {string} s String to encode
|
|
13643
|
+
* @returns {string} Encoded string suitable for use in a PackageURL component
|
|
13644
|
+
*/
|
|
13403
13645
|
export function encodeForPurl(s) {
|
|
13404
13646
|
return s && !s.includes("%40")
|
|
13405
13647
|
? encodeURIComponent(s).replace(/%3A/g, ":").replace(/%2F/g, "/")
|
|
@@ -14303,10 +14545,10 @@ export async function parsePodfileLock(podfileLock, projectPath) {
|
|
|
14303
14545
|
},
|
|
14304
14546
|
];
|
|
14305
14547
|
let podspec = join(projectLocation, `${podName}.podspec`);
|
|
14306
|
-
if (!
|
|
14548
|
+
if (!safeExistsSync(podspec)) {
|
|
14307
14549
|
podspec = `${podspec}.json`;
|
|
14308
14550
|
}
|
|
14309
|
-
if (
|
|
14551
|
+
if (safeExistsSync(podspec)) {
|
|
14310
14552
|
dependency.metadata.properties.push({
|
|
14311
14553
|
name: "cdx:pods:podspecLocation",
|
|
14312
14554
|
value: podspec,
|
|
@@ -15051,6 +15293,19 @@ export function getAtomCommand() {
|
|
|
15051
15293
|
return "atom";
|
|
15052
15294
|
}
|
|
15053
15295
|
|
|
15296
|
+
/**
|
|
15297
|
+
* Execute the atom tool against a source directory or file with the given arguments.
|
|
15298
|
+
*
|
|
15299
|
+
* Resolves the atom binary via `getAtomCommand`, sets up the required environment
|
|
15300
|
+
* (including `JAVA_HOME` from `ATOM_JAVA_HOME` if set), and spawns the process.
|
|
15301
|
+
* Logs diagnostic messages for common failure modes such as unsupported Java versions,
|
|
15302
|
+
* missing `astgen`, and JVM crashes.
|
|
15303
|
+
*
|
|
15304
|
+
* @param {string} src Path to the source directory or file to analyse
|
|
15305
|
+
* @param {string[]} args Arguments to pass to the atom command
|
|
15306
|
+
* @param {Object} extra_env Additional environment variables to merge into the process environment
|
|
15307
|
+
* @returns {boolean} `true` if atom executed successfully and the language is supported; `false` otherwise
|
|
15308
|
+
*/
|
|
15054
15309
|
export function executeAtom(src, args, extra_env = {}) {
|
|
15055
15310
|
const cwd =
|
|
15056
15311
|
safeExistsSync(src) && lstatSync(src).isDirectory() ? src : dirname(src);
|
|
@@ -15266,36 +15521,6 @@ function flattenDeps(dependenciesMap, pkgList, reqOrSetupFile, t) {
|
|
|
15266
15521
|
.sort();
|
|
15267
15522
|
}
|
|
15268
15523
|
|
|
15269
|
-
function get_python_command_from_env(env) {
|
|
15270
|
-
// Virtual environments needs special treatment to use the correct python executable
|
|
15271
|
-
// Without this step, the default python is always used resulting in false positives
|
|
15272
|
-
const python_exe_name = isWin ? "python.exe" : "python";
|
|
15273
|
-
const python3_exe_name = isWin ? "python3.exe" : "python3";
|
|
15274
|
-
let python_cmd_to_use = PYTHON_CMD;
|
|
15275
|
-
if (env.VIRTUAL_ENV) {
|
|
15276
|
-
const bin_dir = isWin ? "Scripts" : "bin";
|
|
15277
|
-
if (safeExistsSync(join(env.VIRTUAL_ENV, bin_dir, python_exe_name))) {
|
|
15278
|
-
python_cmd_to_use = join(env.VIRTUAL_ENV, bin_dir, python_exe_name);
|
|
15279
|
-
} else if (
|
|
15280
|
-
safeExistsSync(join(env.VIRTUAL_ENV, bin_dir, python3_exe_name))
|
|
15281
|
-
) {
|
|
15282
|
-
python_cmd_to_use = join(env.VIRTUAL_ENV, bin_dir, python3_exe_name);
|
|
15283
|
-
}
|
|
15284
|
-
} else if (env.CONDA_PREFIX) {
|
|
15285
|
-
const bin_dir = isWin ? "" : "bin";
|
|
15286
|
-
if (safeExistsSync(join(env.CONDA_PREFIX, bin_dir, python_exe_name))) {
|
|
15287
|
-
python_cmd_to_use = join(env.CONDA_PREFIX, bin_dir, python_exe_name);
|
|
15288
|
-
} else if (
|
|
15289
|
-
safeExistsSync(join(env.CONDA_PREFIX, bin_dir, python3_exe_name))
|
|
15290
|
-
) {
|
|
15291
|
-
python_cmd_to_use = join(env.CONDA_PREFIX, bin_dir, python3_exe_name);
|
|
15292
|
-
}
|
|
15293
|
-
} else if (env.CONDA_PYTHON_EXE) {
|
|
15294
|
-
python_cmd_to_use = env.CONDA_PYTHON_EXE;
|
|
15295
|
-
}
|
|
15296
|
-
return python_cmd_to_use;
|
|
15297
|
-
}
|
|
15298
|
-
|
|
15299
15524
|
/**
|
|
15300
15525
|
* Create uv.lock file with uv sync command.
|
|
15301
15526
|
*
|
|
@@ -15378,14 +15603,12 @@ export async function getPipFrozenTree(
|
|
|
15378
15603
|
...process.env,
|
|
15379
15604
|
};
|
|
15380
15605
|
|
|
15381
|
-
// FIX: Create a set of explicit dependencies from requirements.txt to identify root packages.
|
|
15382
15606
|
const explicitDeps = new Set();
|
|
15383
15607
|
if (reqOrSetupFile?.endsWith(".txt") && safeExistsSync(reqOrSetupFile)) {
|
|
15384
15608
|
// We only need the package names, so we pass `false` to avoid fetching full metadata.
|
|
15385
15609
|
const tempPkgList = await parseReqFile(reqOrSetupFile, null, false);
|
|
15386
15610
|
for (const pkg of tempPkgList) {
|
|
15387
15611
|
if (pkg.name) {
|
|
15388
|
-
// Normalize the name (lowercase, hyphenated) for accurate lookups.
|
|
15389
15612
|
explicitDeps.add(pkg.name.replace(/_/g, "-").toLowerCase());
|
|
15390
15613
|
}
|
|
15391
15614
|
}
|
|
@@ -15441,17 +15664,26 @@ export async function getPipFrozenTree(
|
|
|
15441
15664
|
}
|
|
15442
15665
|
}
|
|
15443
15666
|
}
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15667
|
+
const venvMeta = getVenvMetadata(env);
|
|
15668
|
+
const python_cmd_for_tree = get_python_command_from_env(env);
|
|
15669
|
+
// Check if pyproject.toml is actually a uv-configured workspace
|
|
15670
|
+
let hasToolUv = false;
|
|
15671
|
+
let hasToolPoetry = false;
|
|
15672
|
+
if (
|
|
15673
|
+
reqOrSetupFile?.endsWith("pyproject.toml") &&
|
|
15674
|
+
safeExistsSync(reqOrSetupFile)
|
|
15675
|
+
) {
|
|
15676
|
+
try {
|
|
15677
|
+
const content = readFileSync(reqOrSetupFile, "utf-8");
|
|
15678
|
+
hasToolUv = content.includes("[tool.uv]");
|
|
15679
|
+
hasToolPoetry = content.includes('build-backend = "poetry.core');
|
|
15680
|
+
} catch (_err) {
|
|
15681
|
+
// Ignore read error
|
|
15682
|
+
}
|
|
15683
|
+
}
|
|
15452
15684
|
if (reqOrSetupFile) {
|
|
15453
15685
|
// We have a poetry.lock file
|
|
15454
|
-
if (reqOrSetupFile.endsWith("poetry.lock")) {
|
|
15686
|
+
if (reqOrSetupFile.endsWith("poetry.lock") || hasToolPoetry) {
|
|
15455
15687
|
const poetryConfigArgs = [
|
|
15456
15688
|
"-m",
|
|
15457
15689
|
"poetry",
|
|
@@ -15538,20 +15770,78 @@ export async function getPipFrozenTree(
|
|
|
15538
15770
|
)}${_delimiter}${process.env.PATH || ""}`;
|
|
15539
15771
|
}
|
|
15540
15772
|
}
|
|
15773
|
+
} else if (reqOrSetupFile.endsWith("pdm.lock") || venvMeta.type === "pdm") {
|
|
15774
|
+
thoughtLog("Performing pdm install");
|
|
15775
|
+
result = safeSpawnSync("pdm", ["install"], {
|
|
15776
|
+
cwd: basePath,
|
|
15777
|
+
shell: isWin,
|
|
15778
|
+
env,
|
|
15779
|
+
});
|
|
15780
|
+
if (result.status !== 0 || result.error) {
|
|
15781
|
+
frozen = false;
|
|
15782
|
+
}
|
|
15783
|
+
} else if (
|
|
15784
|
+
reqOrSetupFile.endsWith("pixi.lock") ||
|
|
15785
|
+
venvMeta.type === "pixi"
|
|
15786
|
+
) {
|
|
15787
|
+
thoughtLog("Performing pixi install");
|
|
15788
|
+
result = safeSpawnSync("pixi", ["install"], {
|
|
15789
|
+
cwd: basePath,
|
|
15790
|
+
shell: isWin,
|
|
15791
|
+
env,
|
|
15792
|
+
});
|
|
15793
|
+
if (result.status !== 0 || result.error) {
|
|
15794
|
+
frozen = false;
|
|
15795
|
+
}
|
|
15796
|
+
} else if (
|
|
15797
|
+
reqOrSetupFile.endsWith("uv.lock") ||
|
|
15798
|
+
(venvMeta.type === "uv" && hasToolUv)
|
|
15799
|
+
) {
|
|
15800
|
+
thoughtLog("Performing uv sync");
|
|
15801
|
+
result = safeSpawnSync("uv", ["sync"], {
|
|
15802
|
+
cwd: basePath,
|
|
15803
|
+
shell: isWin,
|
|
15804
|
+
env,
|
|
15805
|
+
});
|
|
15806
|
+
if (result.status !== 0 || result.error) {
|
|
15807
|
+
frozen = false;
|
|
15808
|
+
}
|
|
15809
|
+
} else if (
|
|
15810
|
+
venvMeta.type === "rye" ||
|
|
15811
|
+
reqOrSetupFile.endsWith("requirements.lock")
|
|
15812
|
+
) {
|
|
15813
|
+
thoughtLog("Performing rye sync");
|
|
15814
|
+
result = safeSpawnSync("rye", ["sync"], {
|
|
15815
|
+
cwd: basePath,
|
|
15816
|
+
shell: isWin,
|
|
15817
|
+
env,
|
|
15818
|
+
});
|
|
15819
|
+
if (result.status !== 0 || result.error) {
|
|
15820
|
+
frozen = false;
|
|
15821
|
+
}
|
|
15541
15822
|
} else {
|
|
15542
|
-
//
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
"
|
|
15547
|
-
"pip",
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15823
|
+
// General package installation (Handling pip, or uv pip)
|
|
15824
|
+
let installCmd = python_cmd_for_tree;
|
|
15825
|
+
let pipInstallArgs = [];
|
|
15826
|
+
if (venvMeta.type === "uv") {
|
|
15827
|
+
installCmd = "uv";
|
|
15828
|
+
pipInstallArgs = ["pip", "install"];
|
|
15829
|
+
if (isSecureMode) {
|
|
15830
|
+
pipInstallArgs.push("--only-binary");
|
|
15831
|
+
pipInstallArgs.push(":all:");
|
|
15832
|
+
}
|
|
15833
|
+
} else {
|
|
15834
|
+
pipInstallArgs = [
|
|
15835
|
+
"-m",
|
|
15836
|
+
"pip",
|
|
15837
|
+
"install",
|
|
15838
|
+
"--disable-pip-version-check",
|
|
15839
|
+
];
|
|
15840
|
+
if (isSecureMode) {
|
|
15841
|
+
pipInstallArgs.push("--only-binary=:all:");
|
|
15842
|
+
pipInstallArgs.unshift("-S");
|
|
15843
|
+
}
|
|
15553
15844
|
}
|
|
15554
|
-
// Requirements.txt could be called with any name so best to check for not setup.py and not pyproject.toml
|
|
15555
15845
|
if (
|
|
15556
15846
|
!reqOrSetupFile.endsWith("setup.py") &&
|
|
15557
15847
|
!reqOrSetupFile.endsWith("pyproject.toml")
|
|
@@ -15566,20 +15856,17 @@ export async function getPipFrozenTree(
|
|
|
15566
15856
|
} else {
|
|
15567
15857
|
pipInstallArgs.push(resolve(basePath));
|
|
15568
15858
|
}
|
|
15569
|
-
// Support for passing additional arguments to pip
|
|
15570
|
-
// Eg: --python-version 3.10 --ignore-requires-python --no-warn-conflicts --only-binary=:all:
|
|
15571
15859
|
if (process?.env?.PIP_INSTALL_ARGS) {
|
|
15572
15860
|
const addArgs = process.env.PIP_INSTALL_ARGS.split(" ");
|
|
15573
15861
|
pipInstallArgs = pipInstallArgs.concat(addArgs);
|
|
15574
15862
|
}
|
|
15575
15863
|
thoughtLog(
|
|
15576
|
-
`**
|
|
15864
|
+
`**INSTALL**: Trying package install using the arguments: ${installCmd} ${pipInstallArgs.join(" ")}`,
|
|
15577
15865
|
);
|
|
15578
15866
|
if (DEBUG_MODE) {
|
|
15579
|
-
console.log("Executing",
|
|
15867
|
+
console.log("Executing", installCmd);
|
|
15580
15868
|
}
|
|
15581
|
-
|
|
15582
|
-
result = safeSpawnSync(python_cmd_for_tree, pipInstallArgs, {
|
|
15869
|
+
result = safeSpawnSync(installCmd, pipInstallArgs, {
|
|
15583
15870
|
cwd: basePath,
|
|
15584
15871
|
shell: isWin,
|
|
15585
15872
|
env,
|
|
@@ -15604,6 +15891,10 @@ export async function getPipFrozenTree(
|
|
|
15604
15891
|
);
|
|
15605
15892
|
}
|
|
15606
15893
|
console.log(result.stderr);
|
|
15894
|
+
} else if (result?.stderr?.includes("No module named pip")) {
|
|
15895
|
+
console.log(
|
|
15896
|
+
"Using uv? Ensure 'uv' is in your PATH to allow cdxgen to use `uv pip install` automatically.",
|
|
15897
|
+
);
|
|
15607
15898
|
} else if (
|
|
15608
15899
|
process.env.PIP_INSTALL_ARGS &&
|
|
15609
15900
|
result.stderr?.includes("Cannot set --home and --prefix together")
|
|
@@ -15734,18 +16025,42 @@ export async function getPipFrozenTree(
|
|
|
15734
16025
|
}
|
|
15735
16026
|
// Bug #375. Attempt pip freeze on existing and new virtual environments
|
|
15736
16027
|
if (env.VIRTUAL_ENV?.length || env.CONDA_PREFIX?.length) {
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
16028
|
+
const venvRoot = env.VIRTUAL_ENV || env.CONDA_PREFIX;
|
|
16029
|
+
const binDir = platform() === "win32" ? "Scripts" : "bin";
|
|
16030
|
+
const pipExe = join(
|
|
16031
|
+
venvRoot,
|
|
16032
|
+
binDir,
|
|
16033
|
+
platform() === "win32" ? "pip.exe" : "pip",
|
|
16034
|
+
);
|
|
16035
|
+
if (!safeExistsSync(pipExe)) {
|
|
16036
|
+
thoughtLog(
|
|
16037
|
+
"The 'pip' module is missing in this environment. Bootstrapping it to support piptree extraction.",
|
|
16038
|
+
);
|
|
16039
|
+
if (venvMeta.type === "uv") {
|
|
16040
|
+
safeSpawnSync("uv", ["pip", "install", "pip"], {
|
|
16041
|
+
cwd: basePath,
|
|
16042
|
+
shell: isWin,
|
|
16043
|
+
env,
|
|
16044
|
+
});
|
|
16045
|
+
} else if (venvMeta.type === "rye") {
|
|
16046
|
+
safeSpawnSync("rye", ["run", "pip", "install", "pip"], {
|
|
16047
|
+
cwd: basePath,
|
|
16048
|
+
shell: isWin,
|
|
16049
|
+
env,
|
|
16050
|
+
});
|
|
16051
|
+
} else {
|
|
16052
|
+
safeSpawnSync(python_cmd_for_tree, ["-m", "ensurepip", "--upgrade"], {
|
|
16053
|
+
cwd: basePath,
|
|
16054
|
+
shell: isWin,
|
|
16055
|
+
env,
|
|
16056
|
+
});
|
|
15746
16057
|
}
|
|
15747
16058
|
}
|
|
15748
|
-
|
|
16059
|
+
if (DEBUG_MODE && reqOrSetupFile) {
|
|
16060
|
+
console.log(
|
|
16061
|
+
`About to construct the dependency tree based on ${reqOrSetupFile}. Please wait ...`,
|
|
16062
|
+
);
|
|
16063
|
+
}
|
|
15749
16064
|
// This is a slow step that ideally needs to be invoked only once per venv
|
|
15750
16065
|
const tree = getTreeWithPlugin(env, python_cmd_for_tree, basePath);
|
|
15751
16066
|
if (DEBUG_MODE && !tree.length) {
|
|
@@ -15896,10 +16211,23 @@ export function getPipTreeForPackages(
|
|
|
15896
16211
|
env.PYTHONPATH = undefined;
|
|
15897
16212
|
}
|
|
15898
16213
|
}
|
|
16214
|
+
const venvMeta = getVenvMetadata(env);
|
|
15899
16215
|
const python_cmd_for_tree = get_python_command_from_env(env);
|
|
15900
|
-
let
|
|
15901
|
-
|
|
15902
|
-
|
|
16216
|
+
let installCmd = python_cmd_for_tree;
|
|
16217
|
+
let pipInstallArgs = [];
|
|
16218
|
+
if (venvMeta.type === "uv") {
|
|
16219
|
+
installCmd = "uv";
|
|
16220
|
+
pipInstallArgs = ["pip", "install"];
|
|
16221
|
+
if (isSecureMode) {
|
|
16222
|
+
pipInstallArgs.push("--only-binary");
|
|
16223
|
+
pipInstallArgs.push(":all:");
|
|
16224
|
+
}
|
|
16225
|
+
} else {
|
|
16226
|
+
pipInstallArgs = ["-m", "pip", "install", "--disable-pip-version-check"];
|
|
16227
|
+
if (isSecureMode) {
|
|
16228
|
+
pipInstallArgs.push("--only-binary=:all:");
|
|
16229
|
+
pipInstallArgs.unshift("-S");
|
|
16230
|
+
}
|
|
15903
16231
|
}
|
|
15904
16232
|
// Support for passing additional arguments to pip
|
|
15905
16233
|
// Eg: --python-version 3.10 --ignore-requires-python --no-warn-conflicts
|
|
@@ -15907,19 +16235,23 @@ export function getPipTreeForPackages(
|
|
|
15907
16235
|
const addArgs = process.env.PIP_INSTALL_ARGS.split(" ");
|
|
15908
16236
|
pipInstallArgs = pipInstallArgs.concat(addArgs);
|
|
15909
16237
|
} else {
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
16238
|
+
if (venvMeta.type !== "uv") {
|
|
16239
|
+
pipInstallArgs = pipInstallArgs.concat([
|
|
16240
|
+
"--ignore-requires-python",
|
|
16241
|
+
"--no-compile",
|
|
16242
|
+
"--no-warn-script-location",
|
|
16243
|
+
"--no-warn-conflicts",
|
|
16244
|
+
]);
|
|
16245
|
+
} else {
|
|
16246
|
+
pipInstallArgs.push("--no-compile");
|
|
16247
|
+
}
|
|
15916
16248
|
}
|
|
15917
16249
|
if (DEBUG_MODE) {
|
|
15918
16250
|
console.log(
|
|
15919
16251
|
"Installing",
|
|
15920
16252
|
pkgList.length,
|
|
15921
16253
|
"packages using the command",
|
|
15922
|
-
|
|
16254
|
+
installCmd,
|
|
15923
16255
|
pipInstallArgs.join(" "),
|
|
15924
16256
|
);
|
|
15925
16257
|
}
|
|
@@ -15949,7 +16281,7 @@ export function getPipTreeForPackages(
|
|
|
15949
16281
|
}
|
|
15950
16282
|
// Attempt to perform pip install for pkgSpecifier
|
|
15951
16283
|
const result = safeSpawnSync(
|
|
15952
|
-
|
|
16284
|
+
installCmd,
|
|
15953
16285
|
[...pipInstallArgs, pkgSpecifier],
|
|
15954
16286
|
{
|
|
15955
16287
|
cwd: basePath,
|
|
@@ -15966,6 +16298,30 @@ export function getPipTreeForPackages(
|
|
|
15966
16298
|
}
|
|
15967
16299
|
// Did any package get installed successfully?
|
|
15968
16300
|
if (failedPkgList.length < pkgList.length) {
|
|
16301
|
+
const venvRoot = env.VIRTUAL_ENV || env.CONDA_PREFIX;
|
|
16302
|
+
if (venvRoot) {
|
|
16303
|
+
const binDir = platform() === "win32" ? "Scripts" : "bin";
|
|
16304
|
+
const pipExe = join(
|
|
16305
|
+
venvRoot,
|
|
16306
|
+
binDir,
|
|
16307
|
+
platform() === "win32" ? "pip.exe" : "pip",
|
|
16308
|
+
);
|
|
16309
|
+
if (!safeExistsSync(pipExe)) {
|
|
16310
|
+
if (venvMeta.type === "uv") {
|
|
16311
|
+
safeSpawnSync("uv", ["pip", "install", "pip"], {
|
|
16312
|
+
cwd: basePath,
|
|
16313
|
+
shell: isWin,
|
|
16314
|
+
env,
|
|
16315
|
+
});
|
|
16316
|
+
} else {
|
|
16317
|
+
safeSpawnSync(python_cmd_for_tree, ["-m", "ensurepip", "--upgrade"], {
|
|
16318
|
+
cwd: basePath,
|
|
16319
|
+
shell: isWin,
|
|
16320
|
+
env,
|
|
16321
|
+
});
|
|
16322
|
+
}
|
|
16323
|
+
}
|
|
16324
|
+
}
|
|
15969
16325
|
const dependenciesMap = {};
|
|
15970
16326
|
const tree = getTreeWithPlugin(env, python_cmd_for_tree, basePath);
|
|
15971
16327
|
for (const t of tree) {
|
|
@@ -16033,6 +16389,13 @@ export function getPipTreeForPackages(
|
|
|
16033
16389
|
}
|
|
16034
16390
|
|
|
16035
16391
|
// taken from a very old package https://github.com/keithamus/parse-packagejson-name/blob/master/index.js
|
|
16392
|
+
/**
|
|
16393
|
+
* Parse a package.json `name` field (or a plain string) and extract its scope,
|
|
16394
|
+
* full name, project name, and module name components.
|
|
16395
|
+
*
|
|
16396
|
+
* @param {string|Object} name The package name string or an object with a `name` property
|
|
16397
|
+
* @returns {{ scope: string|null, fullName: string, projectName: string|null, moduleName: string|null }}
|
|
16398
|
+
*/
|
|
16036
16399
|
export function parsePackageJsonName(name) {
|
|
16037
16400
|
const nameRegExp = /^(?:@([^/]+)\/)?(([^.]+)(?:\.(.*))?)$/;
|
|
16038
16401
|
const returnObject = {
|
|
@@ -16169,7 +16532,12 @@ export async function addEvidenceForImports(
|
|
|
16169
16532
|
}
|
|
16170
16533
|
break;
|
|
16171
16534
|
}
|
|
16172
|
-
if (
|
|
16535
|
+
if (
|
|
16536
|
+
impPkgs?.length > 0 &&
|
|
16537
|
+
!isImported &&
|
|
16538
|
+
DEBUG_MODE &&
|
|
16539
|
+
pkg?.scope !== "optional"
|
|
16540
|
+
) {
|
|
16173
16541
|
console.debug(
|
|
16174
16542
|
`\x1b[1;35mNotice: Package ${pkg.name} has no usage in code. Check if it is needed.\x1b[0m`,
|
|
16175
16543
|
);
|
|
@@ -16206,6 +16574,16 @@ export async function addEvidenceForImports(
|
|
|
16206
16574
|
return pkgList;
|
|
16207
16575
|
}
|
|
16208
16576
|
|
|
16577
|
+
/**
|
|
16578
|
+
* Comparator function for sorting CycloneDX component objects.
|
|
16579
|
+
*
|
|
16580
|
+
* Compares components by `bom-ref`, then `purl`, then `name`, using locale-aware
|
|
16581
|
+
* string comparison on the first available key.
|
|
16582
|
+
*
|
|
16583
|
+
* @param {Object|string} a First component to compare
|
|
16584
|
+
* @param {Object|string} b Second component to compare
|
|
16585
|
+
* @returns {number} Negative, zero, or positive integer as required by Array.sort
|
|
16586
|
+
*/
|
|
16209
16587
|
export function componentSorter(a, b) {
|
|
16210
16588
|
if (a && b) {
|
|
16211
16589
|
for (const k of ["bom-ref", "purl", "name"]) {
|
|
@@ -16217,6 +16595,19 @@ export function componentSorter(a, b) {
|
|
|
16217
16595
|
return a.localeCompare(b);
|
|
16218
16596
|
}
|
|
16219
16597
|
|
|
16598
|
+
/**
|
|
16599
|
+
* Parse a CMake-generated dot/graphviz file and extract components and their dependency
|
|
16600
|
+
* relationships.
|
|
16601
|
+
*
|
|
16602
|
+
* The first `digraph` entry becomes the parent component. Subsequent `node` entries
|
|
16603
|
+
* with a `label` attribute are treated as direct dependencies, while commented
|
|
16604
|
+
* `node -> node` relationships are used to construct the dependency graph.
|
|
16605
|
+
*
|
|
16606
|
+
* @param {string} dotFile Path to the CMake-generated dot file
|
|
16607
|
+
* @param {string} pkgType PackageURL type to assign to extracted packages (e.g. `"generic"`)
|
|
16608
|
+
* @param {Object} options CLI options; may contain `projectGroup`, `projectName`, and `projectVersion`
|
|
16609
|
+
* @returns {{ parentComponent: Object, pkgList: Object[], dependenciesList: Object[] }}
|
|
16610
|
+
*/
|
|
16220
16611
|
export function parseCmakeDotFile(dotFile, pkgType, options = {}) {
|
|
16221
16612
|
const dotGraphData = readFileSync(dotFile, { encoding: "utf-8" });
|
|
16222
16613
|
const pkgList = [];
|
|
@@ -16326,6 +16717,19 @@ export function parseCmakeDotFile(dotFile, pkgType, options = {}) {
|
|
|
16326
16717
|
};
|
|
16327
16718
|
}
|
|
16328
16719
|
|
|
16720
|
+
/**
|
|
16721
|
+
* Parse a CMake-like build file (CMakeLists.txt, meson.build, etc.) and extract the
|
|
16722
|
+
* parent component and list of dependency packages.
|
|
16723
|
+
*
|
|
16724
|
+
* Handles `set`, `project`, `find_package`, `find_library`, `find_dependency`,
|
|
16725
|
+
* `find_file`, `FetchContent_MakeAvailable`, and `dependency()` directives.
|
|
16726
|
+
* Uses the MesonWrapDB to improve name resolution confidence.
|
|
16727
|
+
*
|
|
16728
|
+
* @param {string} cmakeListFile Path to the CMake-like build file
|
|
16729
|
+
* @param {string} pkgType PackageURL type to assign to extracted packages (e.g. `"generic"`)
|
|
16730
|
+
* @param {Object} options CLI options; may contain `projectGroup`, `projectName`, and `projectVersion`
|
|
16731
|
+
* @returns {{ parentComponent: Object, pkgList: Object[] }}
|
|
16732
|
+
*/
|
|
16329
16733
|
export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
16330
16734
|
let cmakeListData = readFileSync(cmakeListFile, { encoding: "utf-8" });
|
|
16331
16735
|
const pkgList = [];
|
|
@@ -16579,6 +16983,14 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
|
16579
16983
|
};
|
|
16580
16984
|
}
|
|
16581
16985
|
|
|
16986
|
+
/**
|
|
16987
|
+
* Find the OS package component that provides a given file, by searching the
|
|
16988
|
+
* `PkgProvides` property of each package in the OS package list.
|
|
16989
|
+
*
|
|
16990
|
+
* @param {string} afile Filename or path to look up (matched case-insensitively)
|
|
16991
|
+
* @param {Object[]} osPkgsList Array of OS package component objects to search
|
|
16992
|
+
* @returns {Object|undefined} The matching OS package component, or undefined if not found
|
|
16993
|
+
*/
|
|
16582
16994
|
export function getOSPackageForFile(afile, osPkgsList) {
|
|
16583
16995
|
for (const ospkg of osPkgsList) {
|
|
16584
16996
|
for (const props of ospkg.properties || []) {
|
|
@@ -16760,7 +17172,7 @@ export function getCppModules(src, options, osPkgsList, epkgList) {
|
|
|
16760
17172
|
// Normalize windows separator
|
|
16761
17173
|
afile = afile.replace("..\\", "").replace(/\\/g, "/");
|
|
16762
17174
|
const fileName = basename(afile);
|
|
16763
|
-
if (!fileName
|
|
17175
|
+
if (!fileName?.length) {
|
|
16764
17176
|
continue;
|
|
16765
17177
|
}
|
|
16766
17178
|
const extn = extname(fileName);
|
|
@@ -16997,7 +17409,7 @@ async function queryNuget(p, NUGET_URL) {
|
|
|
16997
17409
|
{ responseType: "json" },
|
|
16998
17410
|
);
|
|
16999
17411
|
const items = res.body.items;
|
|
17000
|
-
if (!items
|
|
17412
|
+
if (!items?.[0]) {
|
|
17001
17413
|
return [np, newBody, body];
|
|
17002
17414
|
}
|
|
17003
17415
|
if (items[0] && !items[0].items) {
|
|
@@ -17192,6 +17604,18 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
|
|
|
17192
17604
|
};
|
|
17193
17605
|
}
|
|
17194
17606
|
|
|
17607
|
+
/**
|
|
17608
|
+
* Enrich .NET package components with occurrence evidence and imported module/method
|
|
17609
|
+
* information from a dosai dependency slices file.
|
|
17610
|
+
*
|
|
17611
|
+
* Builds a mapping of DLL filenames to purls using the `PackageFiles` property of each
|
|
17612
|
+
* package, then reads the slices file to add occurrence locations, imported modules,
|
|
17613
|
+
* called methods, and assembly version information where available.
|
|
17614
|
+
*
|
|
17615
|
+
* @param {Object[]} pkgList Array of .NET package component objects to enrich
|
|
17616
|
+
* @param {string} slicesFile Path to the dosai dependency slices JSON file
|
|
17617
|
+
* @returns {Object[]} The enriched package list (same array, mutated in place)
|
|
17618
|
+
*/
|
|
17195
17619
|
export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
17196
17620
|
// We need two datastructures.
|
|
17197
17621
|
// dll to purl mapping from the pkgList
|
|
@@ -17626,7 +18050,7 @@ export function collectSharedLibs(
|
|
|
17626
18050
|
}
|
|
17627
18051
|
|
|
17628
18052
|
function collectAllLdConfs(basePath, ldConf, allLdConfDirs, libPaths) {
|
|
17629
|
-
if (ldConf &&
|
|
18053
|
+
if (ldConf && safeExistsSync(join(basePath, ldConf))) {
|
|
17630
18054
|
const ldConfData = readFileSync(join(basePath, ldConf), "utf-8");
|
|
17631
18055
|
for (let line of ldConfData.split("\n")) {
|
|
17632
18056
|
line = line.replace("\r", "").trim();
|
|
@@ -17812,6 +18236,14 @@ export function retrieveCdxgenVersion() {
|
|
|
17812
18236
|
return `\x1b[1mCycloneDX Generator ${packageJson.version}\x1b[0m\nRuntime: ${runtimeInfo.runtime}, Version: ${runtimeInfo.version}`;
|
|
17813
18237
|
}
|
|
17814
18238
|
|
|
18239
|
+
/**
|
|
18240
|
+
* Retrieve the version of the cdxgen plugins binary package from package.json.
|
|
18241
|
+
*
|
|
18242
|
+
* Reads the local package.json and searches the `optionalDependencies` for a package
|
|
18243
|
+
* whose name starts with `@cdxgen/cdxgen-plugins-bin`, returning its declared version.
|
|
18244
|
+
*
|
|
18245
|
+
* @returns {string|undefined} Version string of the plugins binary package, or undefined if not found
|
|
18246
|
+
*/
|
|
17815
18247
|
export function retrieveCdxgenPluginVersion() {
|
|
17816
18248
|
const packageJsonAsString = readFileSync(
|
|
17817
18249
|
join(dirNameStr, "package.json"),
|
|
@@ -17876,3 +18308,13 @@ export function splitCommandArgs(commandString) {
|
|
|
17876
18308
|
}
|
|
17877
18309
|
return args;
|
|
17878
18310
|
}
|
|
18311
|
+
|
|
18312
|
+
/**
|
|
18313
|
+
* Convert hyphenated strings to camel case.
|
|
18314
|
+
*
|
|
18315
|
+
* @param {String} str String to convert
|
|
18316
|
+
* @returns {String} camelCased string
|
|
18317
|
+
*/
|
|
18318
|
+
export function toCamel(str) {
|
|
18319
|
+
return str.replace(/-([a-z])/g, (_, g) => g.toUpperCase());
|
|
18320
|
+
}
|