@cyclonedx/cdxgen 12.1.5 → 12.2.1
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 +51 -40
- package/bin/cdxgen.js +194 -97
- package/bin/evinse.js +4 -4
- package/bin/repl.js +1 -1
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +449 -429
- package/lib/cli/index.poku.js +117 -0
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +2 -14
- package/lib/helpers/analyzer.js +606 -3
- package/lib/helpers/analyzer.poku.js +230 -0
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +219 -0
- package/lib/helpers/depsUtils.poku.js +207 -0
- package/lib/helpers/display.js +426 -5
- package/lib/helpers/envcontext.js +18 -3
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +9 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/utils.js +865 -416
- package/lib/helpers/utils.poku.js +172 -265
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +3 -9
- package/lib/parsers/npmrc.js +17 -13
- package/lib/parsers/npmrc.poku.js +41 -5
- package/lib/server/openapi.yaml +34 -1
- package/lib/server/server.js +50 -13
- package/lib/server/server.poku.js +332 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +196 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -9
- 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/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +10 -1
- package/types/lib/helpers/pythonutils.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +533 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +4 -1
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +22 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/lib/stages/pregen/env-audit.js +0 -34
- package/lib/stages/pregen/env-audit.poku.js +0 -290
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/lib/stages/pregen/env-audit.d.ts +0 -2
- package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
package/lib/helpers/utils.js
CHANGED
|
@@ -48,13 +48,13 @@ 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
60
|
import { get_python_command_from_env, getVenvMetadata } from "./pythonutils.js";
|
|
@@ -79,12 +79,6 @@ export const isDeno = globalThis.Deno?.version?.deno !== undefined;
|
|
|
79
79
|
|
|
80
80
|
export const isWin = platform() === "win32";
|
|
81
81
|
export const isMac = platform() === "darwin";
|
|
82
|
-
export let ATOM_DB = join(homedir(), ".local", "share", ".atomdb");
|
|
83
|
-
if (isWin) {
|
|
84
|
-
ATOM_DB = join(homedir(), "AppData", "Local", ".atomdb");
|
|
85
|
-
} else if (isMac) {
|
|
86
|
-
ATOM_DB = join(homedir(), "Library", "Application Support", ".atomdb");
|
|
87
|
-
}
|
|
88
82
|
|
|
89
83
|
/**
|
|
90
84
|
* Safely check if a file path exists without crashing due to a lack of permissions
|
|
@@ -124,14 +118,68 @@ export function safeMkdirSync(filePath, options) {
|
|
|
124
118
|
}
|
|
125
119
|
|
|
126
120
|
export const commandsExecuted = new Set();
|
|
121
|
+
const ALLOW_COMMANDS = (process.env.CDXGEN_ALLOWED_COMMANDS || "").split(",");
|
|
127
122
|
function isAllowedCommand(command) {
|
|
128
123
|
if (!process.env.CDXGEN_ALLOWED_COMMANDS) {
|
|
129
124
|
return true;
|
|
130
125
|
}
|
|
131
|
-
|
|
132
|
-
return allow_commands.includes(command.trim());
|
|
126
|
+
return ALLOW_COMMANDS.includes(command.trim());
|
|
133
127
|
}
|
|
134
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;
|
|
171
|
+
}
|
|
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
|
+
*/
|
|
135
183
|
export function safeSpawnSync(command, args, options) {
|
|
136
184
|
if (
|
|
137
185
|
(isSecureMode && process.permission && !process.permission.has("child")) ||
|
|
@@ -147,6 +195,25 @@ export function safeSpawnSync(command, args, options) {
|
|
|
147
195
|
error: new Error("No execute permission"),
|
|
148
196
|
};
|
|
149
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
|
+
}
|
|
150
217
|
if (!options) {
|
|
151
218
|
options = {};
|
|
152
219
|
}
|
|
@@ -270,6 +337,13 @@ export const DEBUG_MODE =
|
|
|
270
337
|
["debug", "verbose"].includes(process.env.CDXGEN_DEBUG_MODE) ||
|
|
271
338
|
process.env.SCAN_DEBUG_MODE === "debug";
|
|
272
339
|
|
|
340
|
+
// Table border style for console output.
|
|
341
|
+
export const TABLE_BORDER_STYLE = ["ascii", "unicode", "auto"].includes(
|
|
342
|
+
`${process.env.CDXGEN_TABLE_BORDER || ""}`.toLowerCase(),
|
|
343
|
+
)
|
|
344
|
+
? `${process.env.CDXGEN_TABLE_BORDER}`.toLowerCase()
|
|
345
|
+
: "auto";
|
|
346
|
+
|
|
273
347
|
// Timeout milliseconds. Default 20 mins
|
|
274
348
|
export const TIMEOUT_MS =
|
|
275
349
|
Number.parseInt(process.env.CDXGEN_TIMEOUT_MS, 10) || 20 * 60 * 1000;
|
|
@@ -303,6 +377,12 @@ export const PREFER_MAVEN_DEPS_TREE = !["false", "0"].includes(
|
|
|
303
377
|
process.env?.PREFER_MAVEN_DEPS_TREE,
|
|
304
378
|
);
|
|
305
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Determines whether license information should be fetched from remote sources,
|
|
382
|
+
* based on the FETCH_LICENSE environment variable.
|
|
383
|
+
*
|
|
384
|
+
* @returns {boolean} True if the FETCH_LICENSE env var is set to "true" or "1"
|
|
385
|
+
*/
|
|
306
386
|
export function shouldFetchLicense() {
|
|
307
387
|
return (
|
|
308
388
|
process.env.FETCH_LICENSE &&
|
|
@@ -310,6 +390,12 @@ export function shouldFetchLicense() {
|
|
|
310
390
|
);
|
|
311
391
|
}
|
|
312
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Determines whether VCS (version control system) information should be fetched
|
|
395
|
+
* for Go packages, based on the GO_FETCH_VCS environment variable.
|
|
396
|
+
*
|
|
397
|
+
* @returns {boolean} True if the GO_FETCH_VCS env var is set to "true" or "1"
|
|
398
|
+
*/
|
|
313
399
|
export function shouldFetchVCS() {
|
|
314
400
|
return (
|
|
315
401
|
process.env.GO_FETCH_VCS && ["true", "1"].includes(process.env.GO_FETCH_VCS)
|
|
@@ -336,6 +422,12 @@ const MAX_LICENSE_ID_LENGTH = 100;
|
|
|
336
422
|
|
|
337
423
|
export const JAVA_CMD = getJavaCommand();
|
|
338
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Returns the Java executable command to use, resolved in priority order:
|
|
427
|
+
* JAVA_CMD env var > JAVA_HOME/bin/java > "java".
|
|
428
|
+
*
|
|
429
|
+
* @returns {string} Path or name of the Java executable
|
|
430
|
+
*/
|
|
339
431
|
export function getJavaCommand() {
|
|
340
432
|
let javaCmd = "java";
|
|
341
433
|
if (process.env.JAVA_CMD) {
|
|
@@ -352,6 +444,12 @@ export function getJavaCommand() {
|
|
|
352
444
|
|
|
353
445
|
export const PYTHON_CMD = getPythonCommand();
|
|
354
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Returns the Python executable command to use, resolved in priority order:
|
|
449
|
+
* PYTHON_CMD env var > CONDA_PYTHON_EXE env var > "python".
|
|
450
|
+
*
|
|
451
|
+
* @returns {string} Path or name of the Python executable
|
|
452
|
+
*/
|
|
355
453
|
export function getPythonCommand() {
|
|
356
454
|
let pythonCmd = "python";
|
|
357
455
|
if (process.env.PYTHON_CMD) {
|
|
@@ -486,7 +584,6 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
486
584
|
"typescript",
|
|
487
585
|
"ts",
|
|
488
586
|
"tsx",
|
|
489
|
-
"vsix",
|
|
490
587
|
"yarn",
|
|
491
588
|
"rush",
|
|
492
589
|
],
|
|
@@ -581,6 +678,14 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
581
678
|
scala: ["scala", "scala3", "sbt", "mill"],
|
|
582
679
|
nix: ["nix", "nixos", "flake"],
|
|
583
680
|
caxa: ["caxa"],
|
|
681
|
+
"vscode-extension": [
|
|
682
|
+
"vscode-extension",
|
|
683
|
+
"vsix",
|
|
684
|
+
"vscode",
|
|
685
|
+
"openvsx",
|
|
686
|
+
"vscode-extensions",
|
|
687
|
+
"ide-extensions",
|
|
688
|
+
],
|
|
584
689
|
};
|
|
585
690
|
|
|
586
691
|
// Package manager aliases
|
|
@@ -1022,7 +1127,44 @@ export function getLicenses(pkg) {
|
|
|
1022
1127
|
licenseContent.name = l;
|
|
1023
1128
|
}
|
|
1024
1129
|
} else if (Object.keys(l).length) {
|
|
1025
|
-
licenseContent = l;
|
|
1130
|
+
licenseContent = { ...l };
|
|
1131
|
+
if (
|
|
1132
|
+
licenseContent.type &&
|
|
1133
|
+
!licenseContent.id &&
|
|
1134
|
+
!licenseContent.name &&
|
|
1135
|
+
!licenseContent.expression
|
|
1136
|
+
) {
|
|
1137
|
+
if (spdxLicenses.includes(licenseContent.type)) {
|
|
1138
|
+
licenseContent.id = licenseContent.type;
|
|
1139
|
+
} else if (isSpdxLicenseExpression(licenseContent.type)) {
|
|
1140
|
+
licenseContent.expression = licenseContent.type;
|
|
1141
|
+
} else {
|
|
1142
|
+
licenseContent.name = licenseContent.type;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (
|
|
1146
|
+
!licenseContent.id &&
|
|
1147
|
+
!licenseContent.name &&
|
|
1148
|
+
!licenseContent.expression &&
|
|
1149
|
+
licenseContent.url?.startsWith("http")
|
|
1150
|
+
) {
|
|
1151
|
+
const knownLicense = getKnownLicense(licenseContent.url, pkg);
|
|
1152
|
+
if (knownLicense) {
|
|
1153
|
+
if (knownLicense.id) {
|
|
1154
|
+
licenseContent.id = knownLicense.id;
|
|
1155
|
+
} else if (knownLicense.name) {
|
|
1156
|
+
licenseContent.name = knownLicense.name;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (
|
|
1161
|
+
!licenseContent.id &&
|
|
1162
|
+
!licenseContent.name &&
|
|
1163
|
+
!licenseContent.expression
|
|
1164
|
+
) {
|
|
1165
|
+
licenseContent.name = "CUSTOM";
|
|
1166
|
+
}
|
|
1167
|
+
delete licenseContent.type;
|
|
1026
1168
|
} else {
|
|
1027
1169
|
return undefined;
|
|
1028
1170
|
}
|
|
@@ -1158,6 +1300,13 @@ export function readLicenseText(licenseFilepath, licenseContentType) {
|
|
|
1158
1300
|
return null;
|
|
1159
1301
|
}
|
|
1160
1302
|
|
|
1303
|
+
/**
|
|
1304
|
+
* Fetches license information for a list of Swift packages by querying the
|
|
1305
|
+
* GitHub repository license API for packages hosted on github.com.
|
|
1306
|
+
*
|
|
1307
|
+
* @param {Object[]} pkgList List of Swift package objects with optional repository.url fields
|
|
1308
|
+
* @returns {Promise<Object[]>} Resolved list of package objects, each augmented with a license field where available
|
|
1309
|
+
*/
|
|
1161
1310
|
export async function getSwiftPackageMetadata(pkgList) {
|
|
1162
1311
|
const cdepList = [];
|
|
1163
1312
|
for (const p of pkgList) {
|
|
@@ -1242,8 +1391,13 @@ export async function getNpmMetadata(pkgList) {
|
|
|
1242
1391
|
*
|
|
1243
1392
|
* @param {string} pkgJsonFile package.json file
|
|
1244
1393
|
* @param {boolean} simple Return a simpler representation of the component by skipping extended attributes and license fetch.
|
|
1394
|
+
* @param {boolean} securityProps Collect security-related properties
|
|
1245
1395
|
*/
|
|
1246
|
-
export async function parsePkgJson(
|
|
1396
|
+
export async function parsePkgJson(
|
|
1397
|
+
pkgJsonFile,
|
|
1398
|
+
simple = false,
|
|
1399
|
+
securityProps = false,
|
|
1400
|
+
) {
|
|
1247
1401
|
const pkgList = [];
|
|
1248
1402
|
if (safeExistsSync(pkgJsonFile)) {
|
|
1249
1403
|
try {
|
|
@@ -1306,6 +1460,107 @@ export async function parsePkgJson(pkgJsonFile, simple = false) {
|
|
|
1306
1460
|
},
|
|
1307
1461
|
};
|
|
1308
1462
|
}
|
|
1463
|
+
if (securityProps) {
|
|
1464
|
+
if (!apkg.properties) {
|
|
1465
|
+
apkg.properties = [];
|
|
1466
|
+
}
|
|
1467
|
+
// Track executable binaries (potential code execution vectors)
|
|
1468
|
+
if (pkgData.bin) {
|
|
1469
|
+
const binValue =
|
|
1470
|
+
typeof pkgData.bin === "object"
|
|
1471
|
+
? Object.keys(pkgData.bin).join(", ")
|
|
1472
|
+
: pkgData.bin;
|
|
1473
|
+
apkg.properties.push({
|
|
1474
|
+
name: "cdx:npm:bin",
|
|
1475
|
+
value: binValue,
|
|
1476
|
+
});
|
|
1477
|
+
apkg.properties.push({
|
|
1478
|
+
name: "cdx:npm:has_binary",
|
|
1479
|
+
value: "true",
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
// Track lifecycle scripts (preinstall, postinstall, etc. - code execution risk)
|
|
1483
|
+
if (pkgData.scripts && Object.keys(pkgData.scripts).length) {
|
|
1484
|
+
const scriptNames = Object.keys(pkgData.scripts).join(", ");
|
|
1485
|
+
apkg.properties.push({
|
|
1486
|
+
name: "cdx:npm:scripts",
|
|
1487
|
+
value: scriptNames,
|
|
1488
|
+
});
|
|
1489
|
+
// Flag high-risk scripts specifically
|
|
1490
|
+
const riskyScripts = [
|
|
1491
|
+
"preinstall",
|
|
1492
|
+
"install",
|
|
1493
|
+
"postinstall",
|
|
1494
|
+
"prepublish",
|
|
1495
|
+
"prepare",
|
|
1496
|
+
].filter((script) => pkgData.scripts[script]);
|
|
1497
|
+
if (riskyScripts.length) {
|
|
1498
|
+
apkg.properties.push({
|
|
1499
|
+
name: "cdx:npm:risky_scripts",
|
|
1500
|
+
value: riskyScripts.join(", "),
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
// Track platform/architecture constraints
|
|
1505
|
+
if (pkgData.cpu && Array.isArray(pkgData.cpu) && pkgData.cpu.length) {
|
|
1506
|
+
apkg.properties.push({
|
|
1507
|
+
name: "cdx:npm:cpu",
|
|
1508
|
+
value: pkgData.cpu.join(", "),
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
if (pkgData.os && Array.isArray(pkgData.os) && pkgData.os.length) {
|
|
1512
|
+
apkg.properties.push({
|
|
1513
|
+
name: "cdx:npm:os",
|
|
1514
|
+
value: pkgData.os.join(", "),
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
if (
|
|
1518
|
+
pkgData.libc &&
|
|
1519
|
+
Array.isArray(pkgData.libc) &&
|
|
1520
|
+
pkgData.libc.length
|
|
1521
|
+
) {
|
|
1522
|
+
apkg.properties.push({
|
|
1523
|
+
name: "cdx:npm:libc",
|
|
1524
|
+
value: pkgData.libc.join(", "),
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
// Track deprecation notices
|
|
1528
|
+
if (pkgData.deprecated) {
|
|
1529
|
+
apkg.properties.push({
|
|
1530
|
+
name: "cdx:npm:deprecation_notice",
|
|
1531
|
+
value: pkgData.deprecated,
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
// Track if package uses node-gyp (native C/C++ addons = higher risk)
|
|
1535
|
+
if (
|
|
1536
|
+
pkgData.gypfile === true ||
|
|
1537
|
+
pkgData.files?.some((f) => f.endsWith(".gyp") || f.endsWith(".gypi"))
|
|
1538
|
+
) {
|
|
1539
|
+
apkg.properties.push({
|
|
1540
|
+
name: "cdx:npm:gypfile",
|
|
1541
|
+
value: "true",
|
|
1542
|
+
});
|
|
1543
|
+
apkg.properties.push({
|
|
1544
|
+
name: "cdx:npm:native_addon",
|
|
1545
|
+
value: "true",
|
|
1546
|
+
});
|
|
1547
|
+
const nativeDeps = [
|
|
1548
|
+
"nan",
|
|
1549
|
+
"node-addon-api",
|
|
1550
|
+
"bindings",
|
|
1551
|
+
"node-gyp-build",
|
|
1552
|
+
];
|
|
1553
|
+
const foundNativeDeps = Object.keys(
|
|
1554
|
+
pkgData.dependencies || {},
|
|
1555
|
+
).filter((dep) => nativeDeps.includes(dep));
|
|
1556
|
+
if (foundNativeDeps.length) {
|
|
1557
|
+
apkg.properties.push({
|
|
1558
|
+
name: "cdx:npm:native_deps",
|
|
1559
|
+
value: foundNativeDeps.join(", "),
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1309
1564
|
pkgList.push(apkg);
|
|
1310
1565
|
} catch (_err) {
|
|
1311
1566
|
// continue regardless of error
|
|
@@ -1361,7 +1616,8 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1361
1616
|
const srcFilePath = node.path.includes(`${_sep}node_modules`)
|
|
1362
1617
|
? node.path.split(`${_sep}node_modules`)[0]
|
|
1363
1618
|
: node.path;
|
|
1364
|
-
const scope =
|
|
1619
|
+
const scope =
|
|
1620
|
+
node.dev === true || node.optional === true ? "optional" : undefined;
|
|
1365
1621
|
const integrity = node.integrity ? node.integrity : undefined;
|
|
1366
1622
|
|
|
1367
1623
|
let pkg;
|
|
@@ -1434,6 +1690,9 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1434
1690
|
purl: purlString,
|
|
1435
1691
|
"bom-ref": decodeURIComponent(purlString),
|
|
1436
1692
|
};
|
|
1693
|
+
if (node.dev === true) {
|
|
1694
|
+
_setNpmDevelopmentProperty(pkg);
|
|
1695
|
+
}
|
|
1437
1696
|
if (node.resolved) {
|
|
1438
1697
|
if (node.resolved.startsWith("file:")) {
|
|
1439
1698
|
pkg.properties.push({
|
|
@@ -1450,7 +1709,10 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1450
1709
|
name: "ResolvedUrl",
|
|
1451
1710
|
value: node.resolved,
|
|
1452
1711
|
});
|
|
1453
|
-
pkg.
|
|
1712
|
+
pkg.externalReferences.push({
|
|
1713
|
+
type: "distribution",
|
|
1714
|
+
url: node.resolved,
|
|
1715
|
+
});
|
|
1454
1716
|
}
|
|
1455
1717
|
}
|
|
1456
1718
|
if (node.location) {
|
|
@@ -1731,7 +1993,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1731
1993
|
if (!targetVersion || !targetName) {
|
|
1732
1994
|
if (pkgSpecVersionCache[`${edge.name}-${edge.spec}`]) {
|
|
1733
1995
|
targetVersion = pkgSpecVersionCache[`${edge.name}-${edge.spec}`];
|
|
1734
|
-
targetName = edge.name;
|
|
1996
|
+
targetName = edge.name.replace(/-cjs$/, "");
|
|
1735
1997
|
}
|
|
1736
1998
|
}
|
|
1737
1999
|
}
|
|
@@ -2612,6 +2874,51 @@ function _markTreeOptional(
|
|
|
2612
2874
|
}
|
|
2613
2875
|
}
|
|
2614
2876
|
|
|
2877
|
+
function _markTreeDevelopment(
|
|
2878
|
+
dbomRef,
|
|
2879
|
+
dependenciesMap,
|
|
2880
|
+
possibleDevelopmentDeps,
|
|
2881
|
+
visited,
|
|
2882
|
+
) {
|
|
2883
|
+
// Production-required packages set this map entry to false, and that wins
|
|
2884
|
+
// over any later attempt to propagate a development-only marking.
|
|
2885
|
+
if (possibleDevelopmentDeps[dbomRef] === undefined) {
|
|
2886
|
+
possibleDevelopmentDeps[dbomRef] = true;
|
|
2887
|
+
}
|
|
2888
|
+
if (dependenciesMap[dbomRef] && !visited[dbomRef]) {
|
|
2889
|
+
visited[dbomRef] = true;
|
|
2890
|
+
for (const eachDep of dependenciesMap[dbomRef]) {
|
|
2891
|
+
// Undefined means we have not classified this dependency yet, so we
|
|
2892
|
+
// continue propagating the dev-only marking unless it was already proven
|
|
2893
|
+
// to be non-development via a false entry.
|
|
2894
|
+
if (possibleDevelopmentDeps[eachDep] !== false) {
|
|
2895
|
+
_markTreeDevelopment(
|
|
2896
|
+
eachDep,
|
|
2897
|
+
dependenciesMap,
|
|
2898
|
+
possibleDevelopmentDeps,
|
|
2899
|
+
visited,
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
function _setNpmDevelopmentProperty(pkg) {
|
|
2907
|
+
if (!pkg.properties) {
|
|
2908
|
+
pkg.properties = [];
|
|
2909
|
+
}
|
|
2910
|
+
if (
|
|
2911
|
+
!pkg.properties.some((property) => {
|
|
2912
|
+
return property.name === "cdx:npm:package:development";
|
|
2913
|
+
})
|
|
2914
|
+
) {
|
|
2915
|
+
pkg.properties.push({
|
|
2916
|
+
name: "cdx:npm:package:development",
|
|
2917
|
+
value: "true",
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2615
2922
|
function _setTreeWorkspaceRef(
|
|
2616
2923
|
dependenciesMap,
|
|
2617
2924
|
depref,
|
|
@@ -2774,6 +3081,13 @@ function findMatchingWorkspace(workspacePackages, packageName) {
|
|
|
2774
3081
|
);
|
|
2775
3082
|
}
|
|
2776
3083
|
|
|
3084
|
+
/**
|
|
3085
|
+
* Parses the workspaces field from a package.json file and returns the list of
|
|
3086
|
+
* workspace glob patterns. Handles both array and object (with packages key) formats.
|
|
3087
|
+
*
|
|
3088
|
+
* @param {string} packageJsonFile Path to the package.json file to parse
|
|
3089
|
+
* @returns {Object} Object with a packages array of workspace glob patterns, or an empty object on error
|
|
3090
|
+
*/
|
|
2777
3091
|
export function parseYarnWorkspace(packageJsonFile) {
|
|
2778
3092
|
try {
|
|
2779
3093
|
const packageData = JSON.parse(readFileSync(packageJsonFile, "utf-8"));
|
|
@@ -2872,42 +3186,28 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
|
2872
3186
|
if (!pkgList?.length || !lockFilePath) {
|
|
2873
3187
|
return pkgList;
|
|
2874
3188
|
}
|
|
2875
|
-
|
|
2876
3189
|
const baseDir = dirname(lockFilePath);
|
|
2877
3190
|
const nodeModulesDir = join(baseDir, "node_modules");
|
|
2878
|
-
|
|
2879
|
-
// Only proceed if node_modules exists
|
|
2880
3191
|
if (!safeExistsSync(nodeModulesDir)) {
|
|
2881
3192
|
return pkgList;
|
|
2882
3193
|
}
|
|
2883
|
-
|
|
2884
3194
|
if (DEBUG_MODE) {
|
|
2885
3195
|
console.log(
|
|
2886
3196
|
`Metadata for ${pkgList.length} pnpm packages using local node_modules at ${nodeModulesDir}`,
|
|
2887
3197
|
);
|
|
2888
3198
|
}
|
|
2889
|
-
|
|
2890
3199
|
let enhancedCount = 0;
|
|
2891
3200
|
for (const pkg of pkgList) {
|
|
2892
|
-
// Skip if package already has complete metadata
|
|
2893
|
-
if (pkg.description && pkg.author && pkg.license) {
|
|
2894
|
-
continue;
|
|
2895
|
-
}
|
|
2896
|
-
|
|
2897
|
-
// Find the package path in node_modules
|
|
2898
3201
|
const packagePath = findPnpmPackagePath(baseDir, pkg.name, pkg.version);
|
|
2899
3202
|
if (!packagePath) {
|
|
2900
3203
|
continue;
|
|
2901
3204
|
}
|
|
2902
|
-
|
|
2903
3205
|
const packageJsonPath = join(packagePath, "package.json");
|
|
2904
3206
|
if (!safeExistsSync(packageJsonPath)) {
|
|
2905
3207
|
continue;
|
|
2906
3208
|
}
|
|
2907
|
-
|
|
2908
3209
|
try {
|
|
2909
|
-
|
|
2910
|
-
const localPkgList = await parsePkgJson(packageJsonPath, true);
|
|
3210
|
+
const localPkgList = await parsePkgJson(packageJsonPath, true, true);
|
|
2911
3211
|
if (localPkgList && localPkgList.length === 1) {
|
|
2912
3212
|
const localMetadata = localPkgList[0];
|
|
2913
3213
|
if (localMetadata && Object.keys(localMetadata).length) {
|
|
@@ -2926,16 +3226,27 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
|
2926
3226
|
if (!pkg.repository && localMetadata.repository) {
|
|
2927
3227
|
pkg.repository = localMetadata.repository;
|
|
2928
3228
|
}
|
|
2929
|
-
|
|
2930
|
-
// Add a property to track that we enhanced from local node_modules
|
|
2931
3229
|
if (!pkg.properties) {
|
|
2932
3230
|
pkg.properties = [];
|
|
2933
3231
|
}
|
|
3232
|
+
if (localMetadata?.properties?.length) {
|
|
3233
|
+
const seenProperties = new Set(
|
|
3234
|
+
pkg.properties.map(
|
|
3235
|
+
(prop) => `${String(prop?.name)}\u0000${String(prop?.value)}`,
|
|
3236
|
+
),
|
|
3237
|
+
);
|
|
3238
|
+
for (const prop of localMetadata.properties) {
|
|
3239
|
+
const propertyKey = `${String(prop?.name)}\u0000${String(prop?.value)}`;
|
|
3240
|
+
if (!seenProperties.has(propertyKey)) {
|
|
3241
|
+
pkg.properties.push(prop);
|
|
3242
|
+
seenProperties.add(propertyKey);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
2934
3246
|
pkg.properties.push({
|
|
2935
3247
|
name: "LocalNodeModulesPath",
|
|
2936
3248
|
value: packagePath,
|
|
2937
3249
|
});
|
|
2938
|
-
|
|
2939
3250
|
enhancedCount++;
|
|
2940
3251
|
}
|
|
2941
3252
|
}
|
|
@@ -2949,13 +3260,11 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
|
2949
3260
|
}
|
|
2950
3261
|
}
|
|
2951
3262
|
}
|
|
2952
|
-
|
|
2953
3263
|
if (DEBUG_MODE && enhancedCount > 0) {
|
|
2954
3264
|
console.log(
|
|
2955
3265
|
`Enhanced metadata for ${enhancedCount} packages from local node_modules`,
|
|
2956
3266
|
);
|
|
2957
3267
|
}
|
|
2958
|
-
|
|
2959
3268
|
return pkgList;
|
|
2960
3269
|
}
|
|
2961
3270
|
|
|
@@ -2985,6 +3294,7 @@ export async function parsePnpmLock(
|
|
|
2985
3294
|
// See: #1163
|
|
2986
3295
|
// Moreover, we have changed >= 9 for >= 6
|
|
2987
3296
|
// See: discussion #1359
|
|
3297
|
+
const possibleDevelopmentDeps = {};
|
|
2988
3298
|
const possibleOptionalDeps = {};
|
|
2989
3299
|
const dependenciesMap = {};
|
|
2990
3300
|
let ppurl = "";
|
|
@@ -3018,10 +3328,18 @@ export async function parsePnpmLock(
|
|
|
3018
3328
|
}
|
|
3019
3329
|
if (safeExistsSync(pnpmLock)) {
|
|
3020
3330
|
const lockData = readFileSync(pnpmLock, "utf8");
|
|
3021
|
-
|
|
3331
|
+
let yamlObj = parseAllDocuments(lockData);
|
|
3022
3332
|
if (!yamlObj) {
|
|
3023
3333
|
return {};
|
|
3024
3334
|
}
|
|
3335
|
+
if (Array.isArray(yamlObj)) {
|
|
3336
|
+
try {
|
|
3337
|
+
yamlObj = yamlObj[yamlObj.length - 1].toJS();
|
|
3338
|
+
} catch (_e) {
|
|
3339
|
+
console.log(`Unable to parse the pnpm lock file ${pnpmLock}.`);
|
|
3340
|
+
return {};
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3025
3343
|
lockfileVersion = yamlObj.lockfileVersion;
|
|
3026
3344
|
try {
|
|
3027
3345
|
lockfileVersion = Number.parseFloat(lockfileVersion, 10);
|
|
@@ -3069,7 +3387,7 @@ export async function parsePnpmLock(
|
|
|
3069
3387
|
null,
|
|
3070
3388
|
null,
|
|
3071
3389
|
).toString();
|
|
3072
|
-
|
|
3390
|
+
possibleDevelopmentDeps[decodeURIComponent(dpurl)] = true;
|
|
3073
3391
|
}
|
|
3074
3392
|
// Find the root optional and peer dependencies
|
|
3075
3393
|
for (const rdk of Object.keys({ ...rootOptionalDeps, ...rootPeerDeps })) {
|
|
@@ -3093,6 +3411,7 @@ export async function parsePnpmLock(
|
|
|
3093
3411
|
null,
|
|
3094
3412
|
).toString();
|
|
3095
3413
|
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
|
|
3414
|
+
possibleDevelopmentDeps[decodeURIComponent(dpurl)] = false;
|
|
3096
3415
|
}
|
|
3097
3416
|
// Find the root direct dependencies
|
|
3098
3417
|
for (const dk of Object.keys(rootDirectDeps)) {
|
|
@@ -3120,6 +3439,7 @@ export async function parsePnpmLock(
|
|
|
3120
3439
|
// These are direct dependencies so cannot be optional
|
|
3121
3440
|
possibleOptionalDeps[decodeURIComponent(dpurl)] = false;
|
|
3122
3441
|
}
|
|
3442
|
+
possibleDevelopmentDeps[decodeURIComponent(dpurl)] = false;
|
|
3123
3443
|
}
|
|
3124
3444
|
// pnpm-lock.yaml contains more than root dependencies in importers
|
|
3125
3445
|
// we do what we did above but for all the other components
|
|
@@ -3245,6 +3565,7 @@ export async function parsePnpmLock(
|
|
|
3245
3565
|
// This is a definite dependency of this component
|
|
3246
3566
|
comDepList.add(depRef);
|
|
3247
3567
|
possibleOptionalDeps[depRef] = false;
|
|
3568
|
+
possibleDevelopmentDeps[depRef] = false;
|
|
3248
3569
|
// Track the package.json files
|
|
3249
3570
|
if (pkgSrcFile) {
|
|
3250
3571
|
if (!srcFilesMap[depRef]) {
|
|
@@ -3264,7 +3585,7 @@ export async function parsePnpmLock(
|
|
|
3264
3585
|
null,
|
|
3265
3586
|
).toString();
|
|
3266
3587
|
const devDpRef = decodeURIComponent(dpurl);
|
|
3267
|
-
|
|
3588
|
+
possibleDevelopmentDeps[devDpRef] = true;
|
|
3268
3589
|
// This is also a dependency of this component
|
|
3269
3590
|
comDepList.add(devDpRef);
|
|
3270
3591
|
}
|
|
@@ -3282,6 +3603,7 @@ export async function parsePnpmLock(
|
|
|
3282
3603
|
null,
|
|
3283
3604
|
).toString();
|
|
3284
3605
|
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
|
|
3606
|
+
possibleDevelopmentDeps[decodeURIComponent(dpurl)] = false;
|
|
3285
3607
|
}
|
|
3286
3608
|
dependenciesList.push({
|
|
3287
3609
|
ref: decodeURIComponent(compPurl),
|
|
@@ -3320,6 +3642,7 @@ export async function parsePnpmLock(
|
|
|
3320
3642
|
packages[fullName]?.resolution ||
|
|
3321
3643
|
snapshots[fullName]?.resolution;
|
|
3322
3644
|
const integrity = resolution?.integrity;
|
|
3645
|
+
const tarball = resolution?.tarball;
|
|
3323
3646
|
const cpu =
|
|
3324
3647
|
packages[pkgKeys[k]]?.cpu ||
|
|
3325
3648
|
snapshots[pkgKeys[k]]?.cpu ||
|
|
@@ -3483,7 +3806,21 @@ export async function parsePnpmLock(
|
|
|
3483
3806
|
null,
|
|
3484
3807
|
).toString();
|
|
3485
3808
|
const bomRef = decodeURIComponent(purlString);
|
|
3809
|
+
if (
|
|
3810
|
+
packageNode.dev === true &&
|
|
3811
|
+
possibleDevelopmentDeps[bomRef] === undefined
|
|
3812
|
+
) {
|
|
3813
|
+
possibleDevelopmentDeps[bomRef] = true;
|
|
3814
|
+
}
|
|
3486
3815
|
const isBaseOptional = possibleOptionalDeps[bomRef];
|
|
3816
|
+
// optionalDependencies are tracked separately because they may still
|
|
3817
|
+
// be runtime-relevant and should keep the CycloneDX optional scope.
|
|
3818
|
+
// packageNode.dev captures explicit dev-only packages from the lock
|
|
3819
|
+
// entry, while possibleDevelopmentDeps lets that marking propagate to
|
|
3820
|
+
// transitive dependencies discovered through the dependency graph.
|
|
3821
|
+
const isBaseDevelopment =
|
|
3822
|
+
packageNode.dev === true ||
|
|
3823
|
+
possibleDevelopmentDeps[bomRef] === true;
|
|
3487
3824
|
const deplist = [];
|
|
3488
3825
|
for (let dpkgName of Object.keys(deps)) {
|
|
3489
3826
|
let vers = deps[dpkgName];
|
|
@@ -3526,6 +3863,18 @@ export async function parsePnpmLock(
|
|
|
3526
3863
|
{},
|
|
3527
3864
|
);
|
|
3528
3865
|
}
|
|
3866
|
+
if (
|
|
3867
|
+
isBaseDevelopment &&
|
|
3868
|
+
possibleDevelopmentDeps[dbomRef] === undefined
|
|
3869
|
+
) {
|
|
3870
|
+
possibleDevelopmentDeps[dbomRef] = true;
|
|
3871
|
+
_markTreeDevelopment(
|
|
3872
|
+
dbomRef,
|
|
3873
|
+
dependenciesMap,
|
|
3874
|
+
possibleDevelopmentDeps,
|
|
3875
|
+
{},
|
|
3876
|
+
);
|
|
3877
|
+
}
|
|
3529
3878
|
}
|
|
3530
3879
|
if (!dependenciesMap[bomRef]) {
|
|
3531
3880
|
dependenciesMap[bomRef] = [];
|
|
@@ -3538,10 +3887,10 @@ export async function parsePnpmLock(
|
|
|
3538
3887
|
value: pnpmLock,
|
|
3539
3888
|
},
|
|
3540
3889
|
];
|
|
3541
|
-
if (hasBin
|
|
3890
|
+
if (hasBin) {
|
|
3542
3891
|
properties.push({
|
|
3543
3892
|
name: "cdx:npm:has_binary",
|
|
3544
|
-
value:
|
|
3893
|
+
value: "true",
|
|
3545
3894
|
});
|
|
3546
3895
|
}
|
|
3547
3896
|
if (deprecatedMessage) {
|
|
@@ -3554,7 +3903,7 @@ export async function parsePnpmLock(
|
|
|
3554
3903
|
Object.entries(binary_metadata).forEach(([key, value]) => {
|
|
3555
3904
|
if (!value) return;
|
|
3556
3905
|
properties.push({
|
|
3557
|
-
name: `cdx:
|
|
3906
|
+
name: `cdx:npm:${key}`,
|
|
3558
3907
|
value: Array.isArray(value) ? value.join(", ") : value,
|
|
3559
3908
|
});
|
|
3560
3909
|
});
|
|
@@ -3652,6 +4001,14 @@ export async function parsePnpmLock(
|
|
|
3652
4001
|
},
|
|
3653
4002
|
},
|
|
3654
4003
|
};
|
|
4004
|
+
if (tarball) {
|
|
4005
|
+
thePkg.externalReferences = [
|
|
4006
|
+
{
|
|
4007
|
+
type: "distribution",
|
|
4008
|
+
url: tarball,
|
|
4009
|
+
},
|
|
4010
|
+
];
|
|
4011
|
+
}
|
|
3655
4012
|
// Don't add internal workspace packages to the components list
|
|
3656
4013
|
if (thePkg.type !== "application") {
|
|
3657
4014
|
pkgList.push(thePkg);
|
|
@@ -3677,6 +4034,19 @@ export async function parsePnpmLock(
|
|
|
3677
4034
|
}
|
|
3678
4035
|
}
|
|
3679
4036
|
}
|
|
4037
|
+
// Repeat development dependency detection after the dependency graph is fully
|
|
4038
|
+
// built, since a single package iteration can encounter a dev-only component
|
|
4039
|
+
// before its own dependency list has been captured in dependenciesMap.
|
|
4040
|
+
for (const dependencyRef of Object.keys(possibleDevelopmentDeps)) {
|
|
4041
|
+
if (possibleDevelopmentDeps[dependencyRef] === true) {
|
|
4042
|
+
_markTreeDevelopment(
|
|
4043
|
+
dependencyRef,
|
|
4044
|
+
dependenciesMap,
|
|
4045
|
+
possibleDevelopmentDeps,
|
|
4046
|
+
{},
|
|
4047
|
+
);
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
3680
4050
|
|
|
3681
4051
|
// Problem: We might have over aggressively marked a package as optional even it is both required and optional
|
|
3682
4052
|
// The below loops ensure required packages continue to stay required
|
|
@@ -3707,6 +4077,15 @@ export async function parsePnpmLock(
|
|
|
3707
4077
|
if (requiredDependencies[apkg["bom-ref"]]) {
|
|
3708
4078
|
apkg.scope = undefined;
|
|
3709
4079
|
}
|
|
4080
|
+
if (
|
|
4081
|
+
!requiredDependencies[apkg["bom-ref"]] &&
|
|
4082
|
+
possibleDevelopmentDeps[apkg["bom-ref"]]
|
|
4083
|
+
) {
|
|
4084
|
+
if (!apkg.scope) {
|
|
4085
|
+
apkg.scope = "optional";
|
|
4086
|
+
}
|
|
4087
|
+
_setNpmDevelopmentProperty(apkg);
|
|
4088
|
+
}
|
|
3710
4089
|
if (possibleAliasesRefs[apkg["bom-ref"]]) {
|
|
3711
4090
|
apkg.properties.push({
|
|
3712
4091
|
name: "cdx:pnpm:alias",
|
|
@@ -4685,6 +5064,15 @@ export function parseLeinDep(rawOutput) {
|
|
|
4685
5064
|
return [];
|
|
4686
5065
|
}
|
|
4687
5066
|
|
|
5067
|
+
/**
|
|
5068
|
+
* Recursively walks a parsed EDN map node produced by the Leiningen dependency
|
|
5069
|
+
* tree and collects unique dependency entries into the deps array.
|
|
5070
|
+
*
|
|
5071
|
+
* @param {Object} node Parsed EDN node (expected to have a "map" property)
|
|
5072
|
+
* @param {Object} keys_cache Cache object used to deduplicate entries by group-name-version key
|
|
5073
|
+
* @param {Object[]} deps Accumulator array of dependency objects with group, name, and version fields
|
|
5074
|
+
* @returns {Object[]} The populated deps array
|
|
5075
|
+
*/
|
|
4688
5076
|
export function parseLeinMap(node, keys_cache, deps) {
|
|
4689
5077
|
if (node["map"]) {
|
|
4690
5078
|
for (const n of node["map"]) {
|
|
@@ -7302,6 +7690,16 @@ async function getGoPkgVCSUrl(group, name) {
|
|
|
7302
7690
|
return undefined;
|
|
7303
7691
|
}
|
|
7304
7692
|
|
|
7693
|
+
/**
|
|
7694
|
+
* Builds a Go package component object containing purl, bom-ref, integrity hash,
|
|
7695
|
+
* and optionally license and VCS external reference information.
|
|
7696
|
+
*
|
|
7697
|
+
* @param {string} group Package group (module path prefix, may be empty)
|
|
7698
|
+
* @param {string} name Package name (full module path when group is empty)
|
|
7699
|
+
* @param {string} version Package version string
|
|
7700
|
+
* @param {string} hash Integrity hash (e.g. "sha256-…"), used as _integrity
|
|
7701
|
+
* @returns {Promise<Object>} Component object ready for inclusion in a BOM package list
|
|
7702
|
+
*/
|
|
7305
7703
|
export async function getGoPkgComponent(group, name, version, hash) {
|
|
7306
7704
|
let license;
|
|
7307
7705
|
if (shouldFetchLicense()) {
|
|
@@ -7485,6 +7883,15 @@ export async function parseGoModData(goModData, gosumMap) {
|
|
|
7485
7883
|
};
|
|
7486
7884
|
}
|
|
7487
7885
|
|
|
7886
|
+
/**
|
|
7887
|
+
* Parses a Go modules text file (e.g. vendor/modules.txt) and returns a list of
|
|
7888
|
+
* Go package components. Cross-references the go.sum map for integrity hashes and
|
|
7889
|
+
* sets scope and confidence based on hash availability.
|
|
7890
|
+
*
|
|
7891
|
+
* @param {string} txtFile Path to the modules.txt file
|
|
7892
|
+
* @param {Object} gosumMap Map of "module@version" keys to sha256 hash values from go.sum
|
|
7893
|
+
* @returns {Promise<Object[]>} List of Go package component objects with evidence
|
|
7894
|
+
*/
|
|
7488
7895
|
export async function parseGoModulesTxt(txtFile, gosumMap) {
|
|
7489
7896
|
const pkgList = [];
|
|
7490
7897
|
const txtData = readFileSync(txtFile, { encoding: "utf-8" });
|
|
@@ -7888,6 +8295,14 @@ export async function parseGosumData(gosumData) {
|
|
|
7888
8295
|
return pkgList;
|
|
7889
8296
|
}
|
|
7890
8297
|
|
|
8298
|
+
/**
|
|
8299
|
+
* Parses the contents of a Gopkg.lock or Gopkg.toml file (dep tool format) and
|
|
8300
|
+
* returns a list of Go package components. Optionally fetches license information
|
|
8301
|
+
* for each package when FETCH_LICENSE is enabled.
|
|
8302
|
+
*
|
|
8303
|
+
* @param {string} gopkgData Raw string contents of the Gopkg lock/toml file
|
|
8304
|
+
* @returns {Promise<Object[]>} List of Go package component objects
|
|
8305
|
+
*/
|
|
7891
8306
|
export async function parseGopkgData(gopkgData) {
|
|
7892
8307
|
const pkgList = [];
|
|
7893
8308
|
if (!gopkgData) {
|
|
@@ -7937,6 +8352,13 @@ export async function parseGopkgData(gopkgData) {
|
|
|
7937
8352
|
return pkgList;
|
|
7938
8353
|
}
|
|
7939
8354
|
|
|
8355
|
+
/**
|
|
8356
|
+
* Parses the output of `go version -m` (build info) and returns a list of Go
|
|
8357
|
+
* package components for each "dep" line, including name, version, and integrity hash.
|
|
8358
|
+
*
|
|
8359
|
+
* @param {string} buildInfoData Raw string output from `go version -m`
|
|
8360
|
+
* @returns {Promise<Object[]>} List of Go package component objects
|
|
8361
|
+
*/
|
|
7940
8362
|
export async function parseGoVersionData(buildInfoData) {
|
|
7941
8363
|
const pkgList = [];
|
|
7942
8364
|
if (!buildInfoData) {
|
|
@@ -9263,6 +9685,13 @@ export async function parseCargoData(
|
|
|
9263
9685
|
return pkgList;
|
|
9264
9686
|
}
|
|
9265
9687
|
|
|
9688
|
+
/**
|
|
9689
|
+
* Parses a Cargo.lock file's TOML data and returns a flat dependency graph as an
|
|
9690
|
+
* array of objects mapping each package purl to the purls it directly depends on.
|
|
9691
|
+
*
|
|
9692
|
+
* @param {string} cargoLockData Raw TOML string contents of a Cargo.lock file
|
|
9693
|
+
* @returns {Object[]} Array of dependency relationship objects with ref and dependsOn fields
|
|
9694
|
+
*/
|
|
9266
9695
|
export function parseCargoDependencyData(cargoLockData) {
|
|
9267
9696
|
const purlFromPackageInfo = (pkg) =>
|
|
9268
9697
|
decodeURIComponent(
|
|
@@ -9322,6 +9751,14 @@ export function parseCargoDependencyData(cargoLockData) {
|
|
|
9322
9751
|
return result;
|
|
9323
9752
|
}
|
|
9324
9753
|
|
|
9754
|
+
/**
|
|
9755
|
+
* Parses tab-separated cargo-auditable binary metadata output and returns a list
|
|
9756
|
+
* of Rust package components. Optionally fetches crates.io metadata when
|
|
9757
|
+
* FETCH_LICENSE is enabled.
|
|
9758
|
+
*
|
|
9759
|
+
* @param {string} cargoData Tab-separated string output from cargo-auditable or similar tool
|
|
9760
|
+
* @returns {Promise<Object[]>} List of Rust package component objects with group, name, and version
|
|
9761
|
+
*/
|
|
9325
9762
|
export async function parseCargoAuditableData(cargoData) {
|
|
9326
9763
|
const pkgList = [];
|
|
9327
9764
|
if (!cargoData) {
|
|
@@ -9424,6 +9861,13 @@ export async function parsePubLockData(pubLockData, lockFile) {
|
|
|
9424
9861
|
return { rootList, pkgList };
|
|
9425
9862
|
}
|
|
9426
9863
|
|
|
9864
|
+
/**
|
|
9865
|
+
* Parses a Dart pub package's pubspec.yaml content and returns a list containing
|
|
9866
|
+
* a single component object with name, description, version, homepage, and purl.
|
|
9867
|
+
*
|
|
9868
|
+
* @param {string} pubYamlData Raw YAML string contents of a pubspec.yaml file
|
|
9869
|
+
* @returns {Object[]} List containing a single Dart package component object
|
|
9870
|
+
*/
|
|
9427
9871
|
export function parsePubYamlData(pubYamlData) {
|
|
9428
9872
|
const pkgList = [];
|
|
9429
9873
|
let yamlObj;
|
|
@@ -9450,6 +9894,14 @@ export function parsePubYamlData(pubYamlData) {
|
|
|
9450
9894
|
return pkgList;
|
|
9451
9895
|
}
|
|
9452
9896
|
|
|
9897
|
+
/**
|
|
9898
|
+
* Parses Helm chart YAML data (Chart.yaml or repository index.yaml) and returns
|
|
9899
|
+
* a list of Helm chart component objects including the chart itself and any
|
|
9900
|
+
* declared dependencies or index entries.
|
|
9901
|
+
*
|
|
9902
|
+
* @param {string} helmData Raw YAML string contents of a Helm Chart.yaml or index.yaml file
|
|
9903
|
+
* @returns {Object[]} List of Helm chart component objects with name, version, and optional homepage/repository
|
|
9904
|
+
*/
|
|
9453
9905
|
export function parseHelmYamlData(helmData) {
|
|
9454
9906
|
const pkgList = [];
|
|
9455
9907
|
let yamlObj;
|
|
@@ -9515,6 +9967,17 @@ export function parseHelmYamlData(helmData) {
|
|
|
9515
9967
|
return pkgList;
|
|
9516
9968
|
}
|
|
9517
9969
|
|
|
9970
|
+
/**
|
|
9971
|
+
* Recursively walks a parsed YAML/JSON object structure to find container image
|
|
9972
|
+
* references stored under common keys (image, repository, dockerImage, etc.) and
|
|
9973
|
+
* appends discovered image and service entries to pkgList while tracking seen
|
|
9974
|
+
* images in imgList to avoid duplicates.
|
|
9975
|
+
*
|
|
9976
|
+
* @param {Object|Array|string} keyValueObj The object, array, or string node to inspect
|
|
9977
|
+
* @param {Object[]} pkgList Accumulator array that receives {image} and {service} entries
|
|
9978
|
+
* @param {string[]} imgList Accumulator array of image name strings already seen
|
|
9979
|
+
* @returns {string[]} The updated imgList
|
|
9980
|
+
*/
|
|
9518
9981
|
export function recurseImageNameLookup(keyValueObj, pkgList, imgList) {
|
|
9519
9982
|
if (typeof keyValueObj === "string" || keyValueObj instanceof String) {
|
|
9520
9983
|
return imgList;
|
|
@@ -9600,6 +10063,14 @@ function substituteBuildArgs(statement, buildArgs) {
|
|
|
9600
10063
|
return statement;
|
|
9601
10064
|
}
|
|
9602
10065
|
|
|
10066
|
+
/**
|
|
10067
|
+
* Parses the contents of a Dockerfile or Containerfile and returns a list of
|
|
10068
|
+
* base image objects referenced by FROM instructions, substituting ARG default
|
|
10069
|
+
* values where possible and skipping multi-stage build alias references.
|
|
10070
|
+
*
|
|
10071
|
+
* @param {string} fileContents Raw string contents of the Dockerfile/Containerfile
|
|
10072
|
+
* @returns {Object[]} Array of objects with an image property for each unique base image
|
|
10073
|
+
*/
|
|
9603
10074
|
export function parseContainerFile(fileContents) {
|
|
9604
10075
|
const buildArgs = new Map();
|
|
9605
10076
|
const imagesSet = new Set();
|
|
@@ -9667,6 +10138,13 @@ export function parseContainerFile(fileContents) {
|
|
|
9667
10138
|
});
|
|
9668
10139
|
}
|
|
9669
10140
|
|
|
10141
|
+
/**
|
|
10142
|
+
* Parses a Bitbucket Pipelines YAML file and extracts all Docker image references
|
|
10143
|
+
* used as build environments and pipe references (docker:// pipes are normalized).
|
|
10144
|
+
*
|
|
10145
|
+
* @param {string} fileContents Raw string contents of the bitbucket-pipelines.yml file
|
|
10146
|
+
* @returns {Object[]} Array of objects with an image property for each referenced image or pipe
|
|
10147
|
+
*/
|
|
9670
10148
|
export function parseBitbucketPipelinesFile(fileContents) {
|
|
9671
10149
|
const imgList = [];
|
|
9672
10150
|
|
|
@@ -9728,6 +10206,14 @@ export function parseBitbucketPipelinesFile(fileContents) {
|
|
|
9728
10206
|
return imgList;
|
|
9729
10207
|
}
|
|
9730
10208
|
|
|
10209
|
+
/**
|
|
10210
|
+
* Parses container specification data such as Docker Compose files, Kubernetes
|
|
10211
|
+
* manifests, Tekton tasks, Skaffold configs, or Kustomize overlays (YAML, possibly
|
|
10212
|
+
* multi-document) and returns a list of image, service, and OCI spec entries.
|
|
10213
|
+
*
|
|
10214
|
+
* @param {string} dcData Raw YAML string contents of the container spec file
|
|
10215
|
+
* @returns {Object[]} Array of objects with image, service, or ociSpec properties
|
|
10216
|
+
*/
|
|
9731
10217
|
export function parseContainerSpecData(dcData) {
|
|
9732
10218
|
const pkgList = [];
|
|
9733
10219
|
const imgList = [];
|
|
@@ -9795,6 +10281,14 @@ export function parseContainerSpecData(dcData) {
|
|
|
9795
10281
|
return pkgList;
|
|
9796
10282
|
}
|
|
9797
10283
|
|
|
10284
|
+
/**
|
|
10285
|
+
* Identifies the data flow direction of a Privado processing object based on its
|
|
10286
|
+
* sinkId value: "write" sinks map to "inbound", "read" sinks to "outbound", and
|
|
10287
|
+
* HTTP/gRPC sinks to "bi-directional".
|
|
10288
|
+
*
|
|
10289
|
+
* @param {Object} processingObj Privado processing object, expected to have a sinkId property
|
|
10290
|
+
* @returns {string} Flow direction string: "inbound", "outbound", "bi-directional", or "unknown"
|
|
10291
|
+
*/
|
|
9798
10292
|
export function identifyFlow(processingObj) {
|
|
9799
10293
|
let flow = "unknown";
|
|
9800
10294
|
if (processingObj.sinkId) {
|
|
@@ -9821,6 +10315,14 @@ function convertProcessing(processing_list) {
|
|
|
9821
10315
|
return data_list;
|
|
9822
10316
|
}
|
|
9823
10317
|
|
|
10318
|
+
/**
|
|
10319
|
+
* Parses a Privado data flow JSON file and returns a list of service objects
|
|
10320
|
+
* enriched with data classifications, endpoints, trust-boundary flag, violations,
|
|
10321
|
+
* and git metadata properties extracted from the scan result.
|
|
10322
|
+
*
|
|
10323
|
+
* @param {string} f Path to the Privado scan result JSON file
|
|
10324
|
+
* @returns {Object[]} List of service component objects suitable for a SaaSBOM
|
|
10325
|
+
*/
|
|
9824
10326
|
export function parsePrivadoFile(f) {
|
|
9825
10327
|
const pData = readFileSync(f, { encoding: "utf-8" });
|
|
9826
10328
|
const servlist = [];
|
|
@@ -9902,6 +10404,15 @@ export function parsePrivadoFile(f) {
|
|
|
9902
10404
|
return servlist;
|
|
9903
10405
|
}
|
|
9904
10406
|
|
|
10407
|
+
/**
|
|
10408
|
+
* Parses an OpenAPI specification (JSON or YAML string) and returns a list
|
|
10409
|
+
* containing a single service object with name, version, endpoints, and
|
|
10410
|
+
* authentication flag derived from the spec's info, servers, paths, and
|
|
10411
|
+
* securitySchemes sections.
|
|
10412
|
+
*
|
|
10413
|
+
* @param {string} oaData Raw JSON or YAML string contents of an OpenAPI specification
|
|
10414
|
+
* @returns {Object[]} List containing a single service component object
|
|
10415
|
+
*/
|
|
9905
10416
|
export function parseOpenapiSpecData(oaData) {
|
|
9906
10417
|
const servlist = [];
|
|
9907
10418
|
if (!oaData) {
|
|
@@ -9954,6 +10465,13 @@ export function parseOpenapiSpecData(oaData) {
|
|
|
9954
10465
|
return servlist;
|
|
9955
10466
|
}
|
|
9956
10467
|
|
|
10468
|
+
/**
|
|
10469
|
+
* Parses Haskell Cabal freeze file content and extracts package name and version
|
|
10470
|
+
* pairs from constraint lines (lines containing " ==").
|
|
10471
|
+
*
|
|
10472
|
+
* @param {string} cabalData Raw string contents of a Cabal freeze file
|
|
10473
|
+
* @returns {Object[]} List of package objects with name and version fields
|
|
10474
|
+
*/
|
|
9957
10475
|
export function parseCabalData(cabalData) {
|
|
9958
10476
|
const pkgList = [];
|
|
9959
10477
|
if (!cabalData) {
|
|
@@ -9982,6 +10500,13 @@ export function parseCabalData(cabalData) {
|
|
|
9982
10500
|
return pkgList;
|
|
9983
10501
|
}
|
|
9984
10502
|
|
|
10503
|
+
/**
|
|
10504
|
+
* Parses an Elixir mix.lock file and extracts Hex package name and version pairs
|
|
10505
|
+
* from lines containing ":hex".
|
|
10506
|
+
*
|
|
10507
|
+
* @param {string} mixData Raw string contents of a mix.lock file
|
|
10508
|
+
* @returns {Object[]} List of package objects with name and version fields
|
|
10509
|
+
*/
|
|
9985
10510
|
export function parseMixLockData(mixData) {
|
|
9986
10511
|
const pkgList = [];
|
|
9987
10512
|
if (!mixData) {
|
|
@@ -10009,376 +10534,27 @@ export function parseMixLockData(mixData) {
|
|
|
10009
10534
|
return pkgList;
|
|
10010
10535
|
}
|
|
10011
10536
|
|
|
10012
|
-
export function parseGitHubWorkflowData(f) {
|
|
10013
|
-
const pkgList = [];
|
|
10014
|
-
if (!f) {
|
|
10015
|
-
return pkgList;
|
|
10016
|
-
}
|
|
10017
|
-
const ghwData = readFileSync(f, { encoding: "utf-8" });
|
|
10018
|
-
const keys_cache = {};
|
|
10019
|
-
if (!ghwData) {
|
|
10020
|
-
return pkgList;
|
|
10021
|
-
}
|
|
10022
|
-
const yamlObj = _load(ghwData);
|
|
10023
|
-
if (!yamlObj) {
|
|
10024
|
-
return pkgList;
|
|
10025
|
-
}
|
|
10026
|
-
const lines = ghwData.split("\n");
|
|
10027
|
-
// workflow-related values
|
|
10028
|
-
const workflowName = yamlObj.name || path.basename(f, path.extname(f));
|
|
10029
|
-
const workflowTriggers = yamlObj.on || yamlObj.true;
|
|
10030
|
-
const workflowPermissions = yamlObj.permissions || {};
|
|
10031
|
-
let hasWritePermissions = analyzePermissions(workflowPermissions);
|
|
10032
|
-
// GitHub of course supports strings such as "write-all" to make it easy to create supply-chain attacks.
|
|
10033
|
-
if (
|
|
10034
|
-
(typeof workflowPermissions === "string" ||
|
|
10035
|
-
workflowPermissions instanceof String) &&
|
|
10036
|
-
workflowPermissions.includes("write")
|
|
10037
|
-
) {
|
|
10038
|
-
hasWritePermissions = true;
|
|
10039
|
-
}
|
|
10040
|
-
const workflowConcurrency = yamlObj.concurrency || {};
|
|
10041
|
-
const _workflowEnv = yamlObj.env || {};
|
|
10042
|
-
const hasIdTokenWrite = workflowPermissions?.["id-token"] === "write";
|
|
10043
|
-
for (const jobName of Object.keys(yamlObj.jobs)) {
|
|
10044
|
-
const job = yamlObj.jobs[jobName];
|
|
10045
|
-
if (!job.steps) {
|
|
10046
|
-
continue;
|
|
10047
|
-
}
|
|
10048
|
-
// job-related values
|
|
10049
|
-
const jobRunner = job["runs-on"] || "unknown";
|
|
10050
|
-
const jobEnvironment = job.environment?.name || job.environment || "";
|
|
10051
|
-
const jobTimeout = job["timeout-minutes"] || null;
|
|
10052
|
-
const jobPermissions = job.permissions || {};
|
|
10053
|
-
const jobServices = job.services ? Object.keys(job.services) : [];
|
|
10054
|
-
let jobNeeds = job.needs || [];
|
|
10055
|
-
if (!Array.isArray(jobNeeds)) {
|
|
10056
|
-
jobNeeds = [jobNeeds];
|
|
10057
|
-
}
|
|
10058
|
-
const _jobIf = job.if || "";
|
|
10059
|
-
const _jobStrategy = job.strategy ? JSON.stringify(job.strategy) : "";
|
|
10060
|
-
const jobHasWritePermissions = analyzePermissions(jobPermissions);
|
|
10061
|
-
for (const step of job.steps) {
|
|
10062
|
-
if (step.uses) {
|
|
10063
|
-
const tmpA = step.uses.split("@");
|
|
10064
|
-
if (tmpA.length !== 2) {
|
|
10065
|
-
continue;
|
|
10066
|
-
}
|
|
10067
|
-
const groupName = tmpA[0];
|
|
10068
|
-
let name = groupName;
|
|
10069
|
-
let group = "";
|
|
10070
|
-
const tagOrCommit = tmpA[1];
|
|
10071
|
-
let version = tagOrCommit;
|
|
10072
|
-
const tmpB = groupName.split("/");
|
|
10073
|
-
if (tmpB.length >= 2) {
|
|
10074
|
-
name = tmpB.pop();
|
|
10075
|
-
group = tmpB.join("/");
|
|
10076
|
-
} else if (tmpB.length === 1) {
|
|
10077
|
-
name = tmpB[0];
|
|
10078
|
-
group = "";
|
|
10079
|
-
}
|
|
10080
|
-
const versionPinningType = getVersionPinningType(tagOrCommit);
|
|
10081
|
-
const isShaPinned = versionPinningType === "sha";
|
|
10082
|
-
const _isTagPinned = versionPinningType === "tag";
|
|
10083
|
-
const _isBranchRef = versionPinningType === "branch";
|
|
10084
|
-
let lineNum = -1;
|
|
10085
|
-
const stepLineMatch = ghwData.indexOf(step.uses);
|
|
10086
|
-
if (stepLineMatch >= 0) {
|
|
10087
|
-
lineNum = ghwData.substring(0, stepLineMatch).split("\n").length - 1;
|
|
10088
|
-
}
|
|
10089
|
-
if (lineNum >= 0 && lines[lineNum]) {
|
|
10090
|
-
const line = lines[lineNum];
|
|
10091
|
-
const commentMatch = line.match(/#\s*v?([0-9]+(?:\.[0-9]+)*)/);
|
|
10092
|
-
if (commentMatch?.[1]) {
|
|
10093
|
-
version = commentMatch[1];
|
|
10094
|
-
}
|
|
10095
|
-
}
|
|
10096
|
-
const key = `${group}-${name}-${version}`;
|
|
10097
|
-
let confidence = 0.6;
|
|
10098
|
-
if (!keys_cache[key] && name && version) {
|
|
10099
|
-
keys_cache[key] = key;
|
|
10100
|
-
let fullName = name;
|
|
10101
|
-
if (group.length) {
|
|
10102
|
-
fullName = `${group}/${name}`;
|
|
10103
|
-
}
|
|
10104
|
-
let purl = `pkg:github/${fullName}@${version}`;
|
|
10105
|
-
if (tagOrCommit && version !== tagOrCommit) {
|
|
10106
|
-
const qualifierDesc = tagOrCommit.startsWith("v")
|
|
10107
|
-
? "tag"
|
|
10108
|
-
: "commit";
|
|
10109
|
-
purl = `${purl}?${qualifierDesc}=${tagOrCommit}`;
|
|
10110
|
-
confidence = 0.7;
|
|
10111
|
-
}
|
|
10112
|
-
const properties = [
|
|
10113
|
-
{ name: "SrcFile", value: f },
|
|
10114
|
-
{ name: "cdx:github:workflow:name", value: workflowName },
|
|
10115
|
-
{ name: "cdx:github:job:name", value: jobName },
|
|
10116
|
-
{
|
|
10117
|
-
name: "cdx:github:job:runner",
|
|
10118
|
-
value: Array.isArray(jobRunner) ? jobRunner.join(",") : jobRunner,
|
|
10119
|
-
},
|
|
10120
|
-
{ name: "cdx:github:action:uses", value: step.uses },
|
|
10121
|
-
{
|
|
10122
|
-
name: "cdx:github:action:versionPinningType",
|
|
10123
|
-
value: versionPinningType,
|
|
10124
|
-
},
|
|
10125
|
-
{
|
|
10126
|
-
name: "cdx:github:action:isShaPinned",
|
|
10127
|
-
value: isShaPinned.toString(),
|
|
10128
|
-
},
|
|
10129
|
-
];
|
|
10130
|
-
if (step.name) {
|
|
10131
|
-
properties.push({ name: "cdx:github:step:name", value: step.name });
|
|
10132
|
-
}
|
|
10133
|
-
if (step.if) {
|
|
10134
|
-
properties.push({
|
|
10135
|
-
name: "cdx:github:step:condition",
|
|
10136
|
-
value: step.if,
|
|
10137
|
-
});
|
|
10138
|
-
}
|
|
10139
|
-
if (step["continue-on-error"]) {
|
|
10140
|
-
properties.push({
|
|
10141
|
-
name: "cdx:github:step:continueOnError",
|
|
10142
|
-
value: "true",
|
|
10143
|
-
});
|
|
10144
|
-
}
|
|
10145
|
-
if (step.timeout) {
|
|
10146
|
-
properties.push({
|
|
10147
|
-
name: "cdx:github:step:timeout",
|
|
10148
|
-
value: step.timeout.toString(),
|
|
10149
|
-
});
|
|
10150
|
-
}
|
|
10151
|
-
if (jobEnvironment) {
|
|
10152
|
-
properties.push({
|
|
10153
|
-
name: "cdx:github:job:environment",
|
|
10154
|
-
value: jobEnvironment,
|
|
10155
|
-
});
|
|
10156
|
-
}
|
|
10157
|
-
if (jobTimeout) {
|
|
10158
|
-
properties.push({
|
|
10159
|
-
name: "cdx:github:job:timeoutMinutes",
|
|
10160
|
-
value: jobTimeout.toString(),
|
|
10161
|
-
});
|
|
10162
|
-
}
|
|
10163
|
-
if (jobHasWritePermissions) {
|
|
10164
|
-
properties.push({
|
|
10165
|
-
name: "cdx:github:job:hasWritePermissions",
|
|
10166
|
-
value: "true",
|
|
10167
|
-
});
|
|
10168
|
-
}
|
|
10169
|
-
if (jobServices.length > 0) {
|
|
10170
|
-
properties.push({
|
|
10171
|
-
name: "cdx:github:job:services",
|
|
10172
|
-
value: jobServices.join(","),
|
|
10173
|
-
});
|
|
10174
|
-
}
|
|
10175
|
-
if (jobNeeds.length > 0) {
|
|
10176
|
-
properties.push({
|
|
10177
|
-
name: "cdx:github:job:needs",
|
|
10178
|
-
value: jobNeeds.join(","),
|
|
10179
|
-
});
|
|
10180
|
-
}
|
|
10181
|
-
if (hasWritePermissions) {
|
|
10182
|
-
properties.push({
|
|
10183
|
-
name: "cdx:github:workflow:hasWritePermissions",
|
|
10184
|
-
value: "true",
|
|
10185
|
-
});
|
|
10186
|
-
}
|
|
10187
|
-
if (hasIdTokenWrite) {
|
|
10188
|
-
properties.push({
|
|
10189
|
-
name: "cdx:github:workflow:hasIdTokenWrite",
|
|
10190
|
-
value: "true",
|
|
10191
|
-
});
|
|
10192
|
-
}
|
|
10193
|
-
if (workflowConcurrency?.group) {
|
|
10194
|
-
properties.push({
|
|
10195
|
-
name: "cdx:github:workflow:concurrencyGroup",
|
|
10196
|
-
value: workflowConcurrency.group,
|
|
10197
|
-
});
|
|
10198
|
-
}
|
|
10199
|
-
if (group?.startsWith("github/") || group === "actions") {
|
|
10200
|
-
properties.push({ name: "cdx:actions:isOfficial", value: "true" });
|
|
10201
|
-
}
|
|
10202
|
-
if (group?.startsWith("github/")) {
|
|
10203
|
-
properties.push({ name: "cdx:actions:isVerified", value: "true" });
|
|
10204
|
-
}
|
|
10205
|
-
if (workflowTriggers) {
|
|
10206
|
-
const triggers =
|
|
10207
|
-
typeof workflowTriggers === "string"
|
|
10208
|
-
? workflowTriggers
|
|
10209
|
-
: Object.keys(workflowTriggers).join(",");
|
|
10210
|
-
properties.push({
|
|
10211
|
-
name: "cdx:github:workflow:triggers",
|
|
10212
|
-
value: triggers,
|
|
10213
|
-
});
|
|
10214
|
-
}
|
|
10215
|
-
pkgList.push({
|
|
10216
|
-
group,
|
|
10217
|
-
name,
|
|
10218
|
-
version,
|
|
10219
|
-
purl,
|
|
10220
|
-
properties,
|
|
10221
|
-
evidence: {
|
|
10222
|
-
identity: {
|
|
10223
|
-
field: "purl",
|
|
10224
|
-
confidence,
|
|
10225
|
-
methods: [
|
|
10226
|
-
{
|
|
10227
|
-
technique: "source-code-analysis",
|
|
10228
|
-
confidence,
|
|
10229
|
-
value: f,
|
|
10230
|
-
},
|
|
10231
|
-
],
|
|
10232
|
-
},
|
|
10233
|
-
},
|
|
10234
|
-
});
|
|
10235
|
-
}
|
|
10236
|
-
}
|
|
10237
|
-
if (step.run) {
|
|
10238
|
-
const runLineNum = ghwData.indexOf(step.run);
|
|
10239
|
-
const runLine =
|
|
10240
|
-
runLineNum >= 0
|
|
10241
|
-
? ghwData.substring(0, runLineNum).split("\n").length
|
|
10242
|
-
: -1;
|
|
10243
|
-
const pkgCommands = extractPackageManagerCommands(step.run);
|
|
10244
|
-
for (const pkgCmd of pkgCommands) {
|
|
10245
|
-
const key = `run-${pkgCmd.name}-${pkgCmd.version || "latest"}`;
|
|
10246
|
-
if (!keys_cache[key]) {
|
|
10247
|
-
keys_cache[key] = key;
|
|
10248
|
-
pkgList.push({
|
|
10249
|
-
group: "",
|
|
10250
|
-
name: pkgCmd.name,
|
|
10251
|
-
version: pkgCmd.version || undefined,
|
|
10252
|
-
scope: "excluded",
|
|
10253
|
-
"bom-ref": uuidv4(),
|
|
10254
|
-
description: key,
|
|
10255
|
-
properties: [
|
|
10256
|
-
{ name: "SrcFile", value: f },
|
|
10257
|
-
{ name: "cdx:github:workflow:name", value: workflowName },
|
|
10258
|
-
{ name: "cdx:github:job:name", value: jobName },
|
|
10259
|
-
{ name: "cdx:github:step:type", value: "run" },
|
|
10260
|
-
{ name: "cdx:github:step:command", value: pkgCmd.command },
|
|
10261
|
-
{ name: "cdx:github:run:line", value: runLine.toString() },
|
|
10262
|
-
],
|
|
10263
|
-
evidence: {
|
|
10264
|
-
identity: {
|
|
10265
|
-
field: "purl",
|
|
10266
|
-
confidence: 0.5,
|
|
10267
|
-
methods: [
|
|
10268
|
-
{
|
|
10269
|
-
technique: "source-code-analysis",
|
|
10270
|
-
confidence: 0.5,
|
|
10271
|
-
value: f,
|
|
10272
|
-
},
|
|
10273
|
-
],
|
|
10274
|
-
},
|
|
10275
|
-
},
|
|
10276
|
-
});
|
|
10277
|
-
}
|
|
10278
|
-
}
|
|
10279
|
-
}
|
|
10280
|
-
}
|
|
10281
|
-
}
|
|
10282
|
-
return pkgList;
|
|
10283
|
-
}
|
|
10284
|
-
|
|
10285
10537
|
/**
|
|
10286
|
-
*
|
|
10538
|
+
* Parses a GitHub Actions workflow YAML file and returns a list of action
|
|
10539
|
+
* components for each step that uses an external action (steps with a "uses"
|
|
10540
|
+
* field). Each component captures the action name, group, version/commit SHA,
|
|
10541
|
+
* version pinning type, job context (runner, permissions, environment), and
|
|
10542
|
+
* workflow-level metadata (triggers, concurrency, write permissions).
|
|
10287
10543
|
*
|
|
10288
|
-
*
|
|
10289
|
-
|
|
10290
|
-
function analyzePermissions(permissions) {
|
|
10291
|
-
if (!permissions || typeof permissions !== "object") {
|
|
10292
|
-
return false;
|
|
10293
|
-
}
|
|
10294
|
-
const writePermissions = [
|
|
10295
|
-
"actions",
|
|
10296
|
-
"artifact-metadata",
|
|
10297
|
-
"attestations",
|
|
10298
|
-
"checks",
|
|
10299
|
-
"contents",
|
|
10300
|
-
"deployments",
|
|
10301
|
-
"id-token",
|
|
10302
|
-
"models",
|
|
10303
|
-
"discussions",
|
|
10304
|
-
"packages",
|
|
10305
|
-
"pages",
|
|
10306
|
-
"actions",
|
|
10307
|
-
"deployments",
|
|
10308
|
-
"issues",
|
|
10309
|
-
"pull-requests",
|
|
10310
|
-
"security-events",
|
|
10311
|
-
"statuses",
|
|
10312
|
-
];
|
|
10313
|
-
for (const perm of writePermissions) {
|
|
10314
|
-
if (permissions[perm] === "write") {
|
|
10315
|
-
return true;
|
|
10316
|
-
}
|
|
10317
|
-
}
|
|
10318
|
-
return false;
|
|
10319
|
-
}
|
|
10320
|
-
|
|
10321
|
-
/**
|
|
10322
|
-
* Determine version pinning type for security assessment
|
|
10544
|
+
* @param {string} f Path to the GitHub Actions workflow YAML file
|
|
10545
|
+
* @returns {Object[]} List of action component objects with purl, properties, and evidence
|
|
10323
10546
|
*/
|
|
10324
|
-
function
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
}
|
|
10328
|
-
if (/^[a-f0-9]{40}$/.test(versionRef)) {
|
|
10329
|
-
return "sha";
|
|
10330
|
-
}
|
|
10331
|
-
if (/^[a-f0-9]{7,}$/.test(versionRef)) {
|
|
10332
|
-
return "sha";
|
|
10333
|
-
}
|
|
10334
|
-
if (
|
|
10335
|
-
versionRef === "main" ||
|
|
10336
|
-
versionRef === "master" ||
|
|
10337
|
-
versionRef.includes("/")
|
|
10338
|
-
) {
|
|
10339
|
-
return "branch";
|
|
10340
|
-
}
|
|
10341
|
-
return "tag";
|
|
10547
|
+
export function parseGitHubWorkflowData(f) {
|
|
10548
|
+
const { components } = parseWorkflowFile(f);
|
|
10549
|
+
return components.filter((c) => c.scope === "required");
|
|
10342
10550
|
}
|
|
10343
10551
|
|
|
10344
10552
|
/**
|
|
10345
|
-
*
|
|
10553
|
+
* Parse Google Cloud Build YAML data and extract container image steps as packages.
|
|
10554
|
+
*
|
|
10555
|
+
* @param {string} cbwData Raw YAML string of a Cloud Build configuration file
|
|
10556
|
+
* @returns {Object[]} Array of package objects parsed from the build steps
|
|
10346
10557
|
*/
|
|
10347
|
-
function extractPackageManagerCommands(runScript) {
|
|
10348
|
-
const commands = [];
|
|
10349
|
-
if (!runScript) {
|
|
10350
|
-
return commands;
|
|
10351
|
-
}
|
|
10352
|
-
const patterns = [
|
|
10353
|
-
{ regex: /npm\s+(install|i|ci)\s+([^&\n|;]+)/g, name: "npm", type: "npm" },
|
|
10354
|
-
{
|
|
10355
|
-
regex: /yarn\s+(add|install)\s+([^&\n|;]+)/g,
|
|
10356
|
-
name: "yarn",
|
|
10357
|
-
type: "yarn",
|
|
10358
|
-
},
|
|
10359
|
-
{ regex: /pip\s+(install)\s+([^&\n|;]+)/g, name: "pip", type: "pip" },
|
|
10360
|
-
{ regex: /pip3\s+(install)\s+([^&\n|;]+)/g, name: "pip3", type: "pip" },
|
|
10361
|
-
{ regex: /gem\s+(install)\s+([^&\n|;]+)/g, name: "gem", type: "gem" },
|
|
10362
|
-
{ regex: /go\s+(get|install)\s+([^&\n|;]+)/g, name: "go", type: "go" },
|
|
10363
|
-
{
|
|
10364
|
-
regex: /cargo\s+(install|add)\s+([^&\n|;]+)/g,
|
|
10365
|
-
name: "cargo",
|
|
10366
|
-
type: "cargo",
|
|
10367
|
-
},
|
|
10368
|
-
];
|
|
10369
|
-
for (const pattern of patterns) {
|
|
10370
|
-
let match;
|
|
10371
|
-
while ((match = pattern.regex.exec(runScript)) !== null) {
|
|
10372
|
-
commands.push({
|
|
10373
|
-
name: pattern.name,
|
|
10374
|
-
command: match[0],
|
|
10375
|
-
version: null,
|
|
10376
|
-
});
|
|
10377
|
-
}
|
|
10378
|
-
}
|
|
10379
|
-
return commands;
|
|
10380
|
-
}
|
|
10381
|
-
|
|
10382
10558
|
export function parseCloudBuildData(cbwData) {
|
|
10383
10559
|
const pkgList = [];
|
|
10384
10560
|
const keys_cache = {};
|
|
@@ -10455,6 +10631,16 @@ function untilFirst(separator, inputStr) {
|
|
|
10455
10631
|
];
|
|
10456
10632
|
}
|
|
10457
10633
|
|
|
10634
|
+
/**
|
|
10635
|
+
* Map a Conan package reference string to a PackageURL string, name, and version.
|
|
10636
|
+
*
|
|
10637
|
+
* Parses a full Conan package reference of the form
|
|
10638
|
+
* `name/version@user/channel#recipe_revision:package_id#package_revision`
|
|
10639
|
+
* and returns the equivalent purl string together with the extracted name and version.
|
|
10640
|
+
*
|
|
10641
|
+
* @param {string} conanPkgRef Conan package reference string
|
|
10642
|
+
* @returns {Array} Tuple of [purlString, name, version], or [null, null, null] on parse failure
|
|
10643
|
+
*/
|
|
10458
10644
|
export function mapConanPkgRefToPurlStringAndNameAndVersion(conanPkgRef) {
|
|
10459
10645
|
// A full Conan package reference may be composed of the following segments:
|
|
10460
10646
|
// conanPkgRef = "name/version@user/channel#recipe_revision:package_id#package_revision"
|
|
@@ -10580,6 +10766,16 @@ export function mapConanPkgRefToPurlStringAndNameAndVersion(conanPkgRef) {
|
|
|
10580
10766
|
return [purl, info.name, info.version];
|
|
10581
10767
|
}
|
|
10582
10768
|
|
|
10769
|
+
/**
|
|
10770
|
+
* Parse Conan lock file data (conan.lock) and return the package list, dependency map,
|
|
10771
|
+
* and parent component dependencies.
|
|
10772
|
+
*
|
|
10773
|
+
* Supports both the legacy `graph_lock.nodes` format (Conan 1.x) and the newer
|
|
10774
|
+
* `requires` format (Conan 2.x).
|
|
10775
|
+
*
|
|
10776
|
+
* @param {string} conanLockData Raw JSON string of the Conan lock file
|
|
10777
|
+
* @returns {{ pkgList: Object[], dependencies: Object, parentComponentDependencies: string[] }}
|
|
10778
|
+
*/
|
|
10583
10779
|
export function parseConanLockData(conanLockData) {
|
|
10584
10780
|
const pkgList = [];
|
|
10585
10781
|
const dependencies = {};
|
|
@@ -10663,6 +10859,12 @@ export function parseConanLockData(conanLockData) {
|
|
|
10663
10859
|
return { pkgList, dependencies, parentComponentDependencies };
|
|
10664
10860
|
}
|
|
10665
10861
|
|
|
10862
|
+
/**
|
|
10863
|
+
* Parse a Conan conanfile.txt and extract required and optional packages.
|
|
10864
|
+
*
|
|
10865
|
+
* @param {string} conanData Raw text contents of a conanfile.txt
|
|
10866
|
+
* @returns {Object[]} Array of package objects with purl, name, version, and scope
|
|
10867
|
+
*/
|
|
10666
10868
|
export function parseConanData(conanData) {
|
|
10667
10869
|
const pkgList = [];
|
|
10668
10870
|
if (!conanData) {
|
|
@@ -10698,6 +10900,12 @@ export function parseConanData(conanData) {
|
|
|
10698
10900
|
return pkgList;
|
|
10699
10901
|
}
|
|
10700
10902
|
|
|
10903
|
+
/**
|
|
10904
|
+
* Parse Leiningen project.clj data and extract dependency packages.
|
|
10905
|
+
*
|
|
10906
|
+
* @param {string} leinData Raw text contents of a Leiningen project.clj file
|
|
10907
|
+
* @returns {Object[]} Array of package objects with group, name, and version
|
|
10908
|
+
*/
|
|
10701
10909
|
export function parseLeiningenData(leinData) {
|
|
10702
10910
|
const pkgList = [];
|
|
10703
10911
|
if (!leinData) {
|
|
@@ -10732,6 +10940,14 @@ export function parseLeiningenData(leinData) {
|
|
|
10732
10940
|
return pkgList;
|
|
10733
10941
|
}
|
|
10734
10942
|
|
|
10943
|
+
/**
|
|
10944
|
+
* Parse EDN (Extensible Data Notation) deps.edn data and extract dependency packages.
|
|
10945
|
+
*
|
|
10946
|
+
* Handles Clojure deps.edn files, extracting packages listed under the `:deps` key.
|
|
10947
|
+
*
|
|
10948
|
+
* @param {string} rawEdnData Raw EDN text contents of a deps.edn file
|
|
10949
|
+
* @returns {Object[]} Array of package objects with group, name, and version
|
|
10950
|
+
*/
|
|
10735
10951
|
export function parseEdnData(rawEdnData) {
|
|
10736
10952
|
const pkgList = [];
|
|
10737
10953
|
if (!rawEdnData) {
|
|
@@ -10811,7 +11027,7 @@ export function parseFlakeNix(flakeNixFile) {
|
|
|
10811
11027
|
const pkgList = [];
|
|
10812
11028
|
const dependencies = [];
|
|
10813
11029
|
|
|
10814
|
-
if (!
|
|
11030
|
+
if (!safeExistsSync(flakeNixFile)) {
|
|
10815
11031
|
return { pkgList, dependencies };
|
|
10816
11032
|
}
|
|
10817
11033
|
|
|
@@ -10896,7 +11112,7 @@ export function parseFlakeLock(flakeLockFile) {
|
|
|
10896
11112
|
const pkgList = [];
|
|
10897
11113
|
const dependencies = [];
|
|
10898
11114
|
|
|
10899
|
-
if (!
|
|
11115
|
+
if (!safeExistsSync(flakeLockFile)) {
|
|
10900
11116
|
return { pkgList, dependencies };
|
|
10901
11117
|
}
|
|
10902
11118
|
|
|
@@ -11178,6 +11394,13 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
11178
11394
|
};
|
|
11179
11395
|
}
|
|
11180
11396
|
|
|
11397
|
+
/**
|
|
11398
|
+
* Parse a C# packages.config XML file and return a list of NuGet package components.
|
|
11399
|
+
*
|
|
11400
|
+
* @param {string} pkgData Raw XML string of a packages.config file
|
|
11401
|
+
* @param {string} pkgFile Path to the packages.config file, used for evidence properties
|
|
11402
|
+
* @returns {Object[]} Array of NuGet package objects with purl, name, and version
|
|
11403
|
+
*/
|
|
11181
11404
|
export function parseCsPkgData(pkgData, pkgFile) {
|
|
11182
11405
|
const pkgList = [];
|
|
11183
11406
|
if (!pkgData) {
|
|
@@ -11710,6 +11933,17 @@ export function parseCsProjData(
|
|
|
11710
11933
|
};
|
|
11711
11934
|
}
|
|
11712
11935
|
|
|
11936
|
+
/**
|
|
11937
|
+
* Parse a .NET project.assets.json file and return the package list and dependency tree.
|
|
11938
|
+
*
|
|
11939
|
+
* Extracts NuGet packages and their transitive dependency relationships from the
|
|
11940
|
+
* `libraries` and `targets` sections of a project.assets.json file produced by
|
|
11941
|
+
* the .NET restore process.
|
|
11942
|
+
*
|
|
11943
|
+
* @param {string} csProjData Raw JSON string of the project.assets.json file
|
|
11944
|
+
* @param {string} assetsJsonFile Path to the project.assets.json file, used for evidence properties
|
|
11945
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
11946
|
+
*/
|
|
11713
11947
|
export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
11714
11948
|
// extract name, operator, version from .NET package representation
|
|
11715
11949
|
// like "NLog >= 4.5.0"
|
|
@@ -11960,6 +12194,14 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
11960
12194
|
};
|
|
11961
12195
|
}
|
|
11962
12196
|
|
|
12197
|
+
/**
|
|
12198
|
+
* Parse a .NET packages.lock.json file and return the package list, dependency tree,
|
|
12199
|
+
* and list of direct/root dependencies.
|
|
12200
|
+
*
|
|
12201
|
+
* @param {string} csLockData Raw JSON string of the packages.lock.json file
|
|
12202
|
+
* @param {string} pkgLockFile Path to the packages.lock.json file, used for evidence properties
|
|
12203
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[], rootList: Object[] }}
|
|
12204
|
+
*/
|
|
11963
12205
|
export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
11964
12206
|
const pkgList = [];
|
|
11965
12207
|
const dependenciesList = [];
|
|
@@ -12078,6 +12320,14 @@ export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
|
12078
12320
|
};
|
|
12079
12321
|
}
|
|
12080
12322
|
|
|
12323
|
+
/**
|
|
12324
|
+
* Parse a Paket dependency manager lock file (paket.lock) and return the package list
|
|
12325
|
+
* and dependency tree.
|
|
12326
|
+
*
|
|
12327
|
+
* @param {string} paketLockData Raw text contents of the paket.lock file
|
|
12328
|
+
* @param {string} pkgLockFile Path to the paket.lock file, used for evidence properties
|
|
12329
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
12330
|
+
*/
|
|
12081
12331
|
export function parsePaketLockData(paketLockData, pkgLockFile) {
|
|
12082
12332
|
const pkgList = [];
|
|
12083
12333
|
const dependenciesList = [];
|
|
@@ -12252,6 +12502,16 @@ export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
|
12252
12502
|
const rootRequiresMap = {};
|
|
12253
12503
|
if (rootRequires) {
|
|
12254
12504
|
for (const rr of Object.keys(rootRequires)) {
|
|
12505
|
+
// Skip platform requirements (php, hhvm, ext-*, lib-*) — they are never
|
|
12506
|
+
// Composer package names and must not be used to identify root packages.
|
|
12507
|
+
if (
|
|
12508
|
+
rr === "php" ||
|
|
12509
|
+
rr === "hhvm" ||
|
|
12510
|
+
rr.startsWith("ext-") ||
|
|
12511
|
+
rr.startsWith("lib-")
|
|
12512
|
+
) {
|
|
12513
|
+
continue;
|
|
12514
|
+
}
|
|
12255
12515
|
rootRequiresMap[rr] = true;
|
|
12256
12516
|
}
|
|
12257
12517
|
}
|
|
@@ -12403,6 +12663,15 @@ export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
|
12403
12663
|
};
|
|
12404
12664
|
}
|
|
12405
12665
|
|
|
12666
|
+
/**
|
|
12667
|
+
* Parse an sbt dependency tree output file and return the package list and dependency tree.
|
|
12668
|
+
*
|
|
12669
|
+
* Reads a file produced by the sbt `dependencyTree` command and extracts Maven artifact
|
|
12670
|
+
* coordinates, building a hierarchical dependency graph. Evicted packages and ranges are ignored.
|
|
12671
|
+
*
|
|
12672
|
+
* @param {string} sbtTreeFile Path to the sbt dependency tree output file
|
|
12673
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
12674
|
+
*/
|
|
12406
12675
|
export function parseSbtTree(sbtTreeFile) {
|
|
12407
12676
|
const pkgList = [];
|
|
12408
12677
|
const dependenciesList = [];
|
|
@@ -12719,6 +12988,10 @@ export function convertOSQueryResults(
|
|
|
12719
12988
|
if (publisher === "null") {
|
|
12720
12989
|
publisher = "";
|
|
12721
12990
|
}
|
|
12991
|
+
// For vscode-extension purl type, the publisher is used as the namespace
|
|
12992
|
+
if (queryObj.purlType === "vscode-extension" && publisher) {
|
|
12993
|
+
group = publisher.toLowerCase();
|
|
12994
|
+
}
|
|
12722
12995
|
let scope;
|
|
12723
12996
|
const compScope = res.priority;
|
|
12724
12997
|
if (["required", "optional", "excluded"].includes(compScope)) {
|
|
@@ -12819,6 +13092,17 @@ export function convertOSQueryResults(
|
|
|
12819
13092
|
return pkgList;
|
|
12820
13093
|
}
|
|
12821
13094
|
|
|
13095
|
+
/**
|
|
13096
|
+
* Create a PackageURL object from a repository URL string, package type, and version.
|
|
13097
|
+
*
|
|
13098
|
+
* Supports HTTPS URLs, SSH `git@` URLs, Bitbucket SSH URLs, and local paths.
|
|
13099
|
+
* Extracts the namespace (host + path prefix) and repository name from the URL.
|
|
13100
|
+
*
|
|
13101
|
+
* @param {string} type PackageURL type (e.g. `"swift"`, `"generic"`)
|
|
13102
|
+
* @param {string} repoUrl Repository URL string
|
|
13103
|
+
* @param {string} version Package version
|
|
13104
|
+
* @returns {PackageURL|undefined} PackageURL object, or undefined for unsupported URL formats
|
|
13105
|
+
*/
|
|
12822
13106
|
export function purlFromUrlString(type, repoUrl, version) {
|
|
12823
13107
|
let namespace = "";
|
|
12824
13108
|
let name;
|
|
@@ -13111,6 +13395,20 @@ export async function collectMvnDependencies(
|
|
|
13111
13395
|
return jarNSMapping;
|
|
13112
13396
|
}
|
|
13113
13397
|
|
|
13398
|
+
/**
|
|
13399
|
+
* Collect Gradle project dependencies by scanning the Gradle cache directory for JAR files
|
|
13400
|
+
* and their associated POM files.
|
|
13401
|
+
*
|
|
13402
|
+
* Uses the `GRADLE_CACHE_DIR` or `GRADLE_USER_HOME` environment variables to locate the
|
|
13403
|
+
* Gradle files-2.1 cache, then delegates to {@link collectJarNS} to extract namespace
|
|
13404
|
+
* and purl information from those JARs.
|
|
13405
|
+
*
|
|
13406
|
+
* @param {string} _gradleCmd Gradle command (unused; reserved for future use)
|
|
13407
|
+
* @param {string} _basePath Base project path (unused; reserved for future use)
|
|
13408
|
+
* @param {boolean} _cleanup Whether to clean up temporary files (unused; reserved for future use)
|
|
13409
|
+
* @param {boolean} _includeCacheDir Whether to include cache directory (unused; reserved for future use)
|
|
13410
|
+
* @returns {Promise<Object>} JAR namespace mapping object returned by collectJarNS
|
|
13411
|
+
*/
|
|
13114
13412
|
export async function collectGradleDependencies(
|
|
13115
13413
|
_gradleCmd,
|
|
13116
13414
|
_basePath,
|
|
@@ -13324,6 +13622,16 @@ export async function collectJarNS(jarPath, pomPathMap = {}) {
|
|
|
13324
13622
|
return jarNSMapping;
|
|
13325
13623
|
}
|
|
13326
13624
|
|
|
13625
|
+
/**
|
|
13626
|
+
* Convert a JAR namespace mapping (produced by {@link collectJarNS}) into an array
|
|
13627
|
+
* of CycloneDX package component objects.
|
|
13628
|
+
*
|
|
13629
|
+
* Each entry in the mapping is resolved to a component with name, group, version,
|
|
13630
|
+
* purl, hashes, namespace properties, and source file evidence.
|
|
13631
|
+
*
|
|
13632
|
+
* @param {Object} jarNSMapping Map of purl string to `{ jarFile, pom, namespaces, hashes }`
|
|
13633
|
+
* @returns {Promise<Object[]>} Array of component objects derived from the JAR mapping
|
|
13634
|
+
*/
|
|
13327
13635
|
export async function convertJarNSToPackages(jarNSMapping) {
|
|
13328
13636
|
const pkgList = [];
|
|
13329
13637
|
for (const purl of Object.keys(jarNSMapping)) {
|
|
@@ -13427,6 +13735,12 @@ export function parsePomXml(pomXmlData) {
|
|
|
13427
13735
|
return undefined;
|
|
13428
13736
|
}
|
|
13429
13737
|
|
|
13738
|
+
/**
|
|
13739
|
+
* Parse a JAR MANIFEST.MF file and return its key-value pairs as an object.
|
|
13740
|
+
*
|
|
13741
|
+
* @param {string} jarMetadata Raw text contents of a MANIFEST.MF file
|
|
13742
|
+
* @returns {Object} Key-value pairs extracted from the manifest
|
|
13743
|
+
*/
|
|
13430
13744
|
export function parseJarManifest(jarMetadata) {
|
|
13431
13745
|
const metadata = {};
|
|
13432
13746
|
if (!jarMetadata) {
|
|
@@ -13444,6 +13758,12 @@ export function parseJarManifest(jarMetadata) {
|
|
|
13444
13758
|
return metadata;
|
|
13445
13759
|
}
|
|
13446
13760
|
|
|
13761
|
+
/**
|
|
13762
|
+
* Parse a Maven pom.properties file and return its key-value pairs as an object.
|
|
13763
|
+
*
|
|
13764
|
+
* @param {string} pomProperties Raw text contents of a pom.properties file
|
|
13765
|
+
* @returns {Object} Key-value pairs extracted from the properties file
|
|
13766
|
+
*/
|
|
13447
13767
|
export function parsePomProperties(pomProperties) {
|
|
13448
13768
|
const properties = {};
|
|
13449
13769
|
if (!pomProperties) {
|
|
@@ -13461,6 +13781,13 @@ export function parsePomProperties(pomProperties) {
|
|
|
13461
13781
|
return properties;
|
|
13462
13782
|
}
|
|
13463
13783
|
|
|
13784
|
+
/**
|
|
13785
|
+
* Encode a string for safe inclusion in a PackageURL, percent-encoding special characters
|
|
13786
|
+
* while preserving already-encoded `%40` sequences and keeping `:` and `/` unencoded.
|
|
13787
|
+
*
|
|
13788
|
+
* @param {string} s String to encode
|
|
13789
|
+
* @returns {string} Encoded string suitable for use in a PackageURL component
|
|
13790
|
+
*/
|
|
13464
13791
|
export function encodeForPurl(s) {
|
|
13465
13792
|
return s && !s.includes("%40")
|
|
13466
13793
|
? encodeURIComponent(s).replace(/%3A/g, ":").replace(/%2F/g, "/")
|
|
@@ -14364,10 +14691,10 @@ export async function parsePodfileLock(podfileLock, projectPath) {
|
|
|
14364
14691
|
},
|
|
14365
14692
|
];
|
|
14366
14693
|
let podspec = join(projectLocation, `${podName}.podspec`);
|
|
14367
|
-
if (!
|
|
14694
|
+
if (!safeExistsSync(podspec)) {
|
|
14368
14695
|
podspec = `${podspec}.json`;
|
|
14369
14696
|
}
|
|
14370
|
-
if (
|
|
14697
|
+
if (safeExistsSync(podspec)) {
|
|
14371
14698
|
dependency.metadata.properties.push({
|
|
14372
14699
|
name: "cdx:pods:podspecLocation",
|
|
14373
14700
|
value: podspec,
|
|
@@ -15112,6 +15439,19 @@ export function getAtomCommand() {
|
|
|
15112
15439
|
return "atom";
|
|
15113
15440
|
}
|
|
15114
15441
|
|
|
15442
|
+
/**
|
|
15443
|
+
* Execute the atom tool against a source directory or file with the given arguments.
|
|
15444
|
+
*
|
|
15445
|
+
* Resolves the atom binary via `getAtomCommand`, sets up the required environment
|
|
15446
|
+
* (including `JAVA_HOME` from `ATOM_JAVA_HOME` if set), and spawns the process.
|
|
15447
|
+
* Logs diagnostic messages for common failure modes such as unsupported Java versions,
|
|
15448
|
+
* missing `astgen`, and JVM crashes.
|
|
15449
|
+
*
|
|
15450
|
+
* @param {string} src Path to the source directory or file to analyse
|
|
15451
|
+
* @param {string[]} args Arguments to pass to the atom command
|
|
15452
|
+
* @param {Object} extra_env Additional environment variables to merge into the process environment
|
|
15453
|
+
* @returns {boolean} `true` if atom executed successfully and the language is supported; `false` otherwise
|
|
15454
|
+
*/
|
|
15115
15455
|
export function executeAtom(src, args, extra_env = {}) {
|
|
15116
15456
|
const cwd =
|
|
15117
15457
|
safeExistsSync(src) && lstatSync(src).isDirectory() ? src : dirname(src);
|
|
@@ -16195,6 +16535,13 @@ export function getPipTreeForPackages(
|
|
|
16195
16535
|
}
|
|
16196
16536
|
|
|
16197
16537
|
// taken from a very old package https://github.com/keithamus/parse-packagejson-name/blob/master/index.js
|
|
16538
|
+
/**
|
|
16539
|
+
* Parse a package.json `name` field (or a plain string) and extract its scope,
|
|
16540
|
+
* full name, project name, and module name components.
|
|
16541
|
+
*
|
|
16542
|
+
* @param {string|Object} name The package name string or an object with a `name` property
|
|
16543
|
+
* @returns {{ scope: string|null, fullName: string, projectName: string|null, moduleName: string|null }}
|
|
16544
|
+
*/
|
|
16198
16545
|
export function parsePackageJsonName(name) {
|
|
16199
16546
|
const nameRegExp = /^(?:@([^/]+)\/)?(([^.]+)(?:\.(.*))?)$/;
|
|
16200
16547
|
const returnObject = {
|
|
@@ -16246,6 +16593,7 @@ export async function addEvidenceForImports(
|
|
|
16246
16593
|
: [name];
|
|
16247
16594
|
let isImported = false;
|
|
16248
16595
|
for (const alias of aliases) {
|
|
16596
|
+
const isWasmAlias = /\.wasm([?#].*)?$/i.test(alias);
|
|
16249
16597
|
const all_includes = impPkgs.filter(
|
|
16250
16598
|
(find_pkg) =>
|
|
16251
16599
|
find_pkg.startsWith(alias) &&
|
|
@@ -16255,12 +16603,20 @@ export async function addEvidenceForImports(
|
|
|
16255
16603
|
find_pkg.startsWith(alias),
|
|
16256
16604
|
);
|
|
16257
16605
|
if (all_exports?.length) {
|
|
16258
|
-
let exportedModules = new Set(all_exports);
|
|
16606
|
+
let exportedModules = new Set(isWasmAlias ? [] : all_exports);
|
|
16259
16607
|
pkg.properties = pkg.properties || [];
|
|
16260
16608
|
for (const subevidence of all_exports) {
|
|
16261
16609
|
const evidences = allExports[subevidence];
|
|
16262
16610
|
for (const evidence of evidences) {
|
|
16263
16611
|
if (evidence && Object.keys(evidence).length) {
|
|
16612
|
+
if (isWasmAlias) {
|
|
16613
|
+
for (const wasmImportedModule of evidence.importedModules ||
|
|
16614
|
+
[]) {
|
|
16615
|
+
if (wasmImportedModule?.length) {
|
|
16616
|
+
exportedModules.add(wasmImportedModule);
|
|
16617
|
+
}
|
|
16618
|
+
}
|
|
16619
|
+
}
|
|
16264
16620
|
if (evidence.exportedModules.length > 1) {
|
|
16265
16621
|
for (const aexpsubm of evidence.exportedModules) {
|
|
16266
16622
|
// Be selective on the submodule names
|
|
@@ -16295,6 +16651,8 @@ export async function addEvidenceForImports(
|
|
|
16295
16651
|
if (impPkgs.includes(alias) || all_includes.length) {
|
|
16296
16652
|
isImported = true;
|
|
16297
16653
|
let importedModules = new Set();
|
|
16654
|
+
let wasmExportedModules = new Set();
|
|
16655
|
+
const seenOccurrenceLocations = new Set();
|
|
16298
16656
|
pkg.scope = "required";
|
|
16299
16657
|
for (const subevidence of all_includes) {
|
|
16300
16658
|
const evidences = allImports[subevidence];
|
|
@@ -16302,16 +16660,23 @@ export async function addEvidenceForImports(
|
|
|
16302
16660
|
if (evidence && Object.keys(evidence).length && evidence.fileName) {
|
|
16303
16661
|
pkg.evidence = pkg.evidence || {};
|
|
16304
16662
|
pkg.evidence.occurrences = pkg.evidence.occurrences || [];
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16663
|
+
const occurrenceLocation = `${evidence.fileName}${
|
|
16664
|
+
evidence.lineNumber ? `#${evidence.lineNumber}` : ""
|
|
16665
|
+
}`;
|
|
16666
|
+
if (!seenOccurrenceLocations.has(occurrenceLocation)) {
|
|
16667
|
+
pkg.evidence.occurrences.push({
|
|
16668
|
+
location: occurrenceLocation,
|
|
16669
|
+
});
|
|
16670
|
+
seenOccurrenceLocations.add(occurrenceLocation);
|
|
16671
|
+
}
|
|
16310
16672
|
importedModules.add(evidence.importedAs);
|
|
16311
16673
|
for (const importedSm of evidence.importedModules || []) {
|
|
16312
16674
|
if (!importedSm) {
|
|
16313
16675
|
continue;
|
|
16314
16676
|
}
|
|
16677
|
+
if (isWasmAlias) {
|
|
16678
|
+
wasmExportedModules.add(importedSm);
|
|
16679
|
+
}
|
|
16315
16680
|
// Store both the short and long form of the imported sub modules
|
|
16316
16681
|
if (importedSm.length > 3) {
|
|
16317
16682
|
importedModules.add(importedSm);
|
|
@@ -16322,6 +16687,7 @@ export async function addEvidenceForImports(
|
|
|
16322
16687
|
}
|
|
16323
16688
|
}
|
|
16324
16689
|
importedModules = Array.from(importedModules);
|
|
16690
|
+
wasmExportedModules = Array.from(wasmExportedModules);
|
|
16325
16691
|
if (importedModules.length) {
|
|
16326
16692
|
pkg.properties = pkg.properties || [];
|
|
16327
16693
|
pkg.properties.push({
|
|
@@ -16329,6 +16695,15 @@ export async function addEvidenceForImports(
|
|
|
16329
16695
|
value: importedModules.join(","),
|
|
16330
16696
|
});
|
|
16331
16697
|
}
|
|
16698
|
+
if (isWasmAlias && wasmExportedModules.length) {
|
|
16699
|
+
pkg.properties = pkg.properties || [];
|
|
16700
|
+
if (!pkg.properties.some((p) => p.name === "ExportedModules")) {
|
|
16701
|
+
pkg.properties.push({
|
|
16702
|
+
name: "ExportedModules",
|
|
16703
|
+
value: wasmExportedModules.join(","),
|
|
16704
|
+
});
|
|
16705
|
+
}
|
|
16706
|
+
}
|
|
16332
16707
|
break;
|
|
16333
16708
|
}
|
|
16334
16709
|
if (
|
|
@@ -16373,6 +16748,16 @@ export async function addEvidenceForImports(
|
|
|
16373
16748
|
return pkgList;
|
|
16374
16749
|
}
|
|
16375
16750
|
|
|
16751
|
+
/**
|
|
16752
|
+
* Comparator function for sorting CycloneDX component objects.
|
|
16753
|
+
*
|
|
16754
|
+
* Compares components by `bom-ref`, then `purl`, then `name`, using locale-aware
|
|
16755
|
+
* string comparison on the first available key.
|
|
16756
|
+
*
|
|
16757
|
+
* @param {Object|string} a First component to compare
|
|
16758
|
+
* @param {Object|string} b Second component to compare
|
|
16759
|
+
* @returns {number} Negative, zero, or positive integer as required by Array.sort
|
|
16760
|
+
*/
|
|
16376
16761
|
export function componentSorter(a, b) {
|
|
16377
16762
|
if (a && b) {
|
|
16378
16763
|
for (const k of ["bom-ref", "purl", "name"]) {
|
|
@@ -16384,6 +16769,19 @@ export function componentSorter(a, b) {
|
|
|
16384
16769
|
return a.localeCompare(b);
|
|
16385
16770
|
}
|
|
16386
16771
|
|
|
16772
|
+
/**
|
|
16773
|
+
* Parse a CMake-generated dot/graphviz file and extract components and their dependency
|
|
16774
|
+
* relationships.
|
|
16775
|
+
*
|
|
16776
|
+
* The first `digraph` entry becomes the parent component. Subsequent `node` entries
|
|
16777
|
+
* with a `label` attribute are treated as direct dependencies, while commented
|
|
16778
|
+
* `node -> node` relationships are used to construct the dependency graph.
|
|
16779
|
+
*
|
|
16780
|
+
* @param {string} dotFile Path to the CMake-generated dot file
|
|
16781
|
+
* @param {string} pkgType PackageURL type to assign to extracted packages (e.g. `"generic"`)
|
|
16782
|
+
* @param {Object} options CLI options; may contain `projectGroup`, `projectName`, and `projectVersion`
|
|
16783
|
+
* @returns {{ parentComponent: Object, pkgList: Object[], dependenciesList: Object[] }}
|
|
16784
|
+
*/
|
|
16387
16785
|
export function parseCmakeDotFile(dotFile, pkgType, options = {}) {
|
|
16388
16786
|
const dotGraphData = readFileSync(dotFile, { encoding: "utf-8" });
|
|
16389
16787
|
const pkgList = [];
|
|
@@ -16493,6 +16891,19 @@ export function parseCmakeDotFile(dotFile, pkgType, options = {}) {
|
|
|
16493
16891
|
};
|
|
16494
16892
|
}
|
|
16495
16893
|
|
|
16894
|
+
/**
|
|
16895
|
+
* Parse a CMake-like build file (CMakeLists.txt, meson.build, etc.) and extract the
|
|
16896
|
+
* parent component and list of dependency packages.
|
|
16897
|
+
*
|
|
16898
|
+
* Handles `set`, `project`, `find_package`, `find_library`, `find_dependency`,
|
|
16899
|
+
* `find_file`, `FetchContent_MakeAvailable`, and `dependency()` directives.
|
|
16900
|
+
* Uses the MesonWrapDB to improve name resolution confidence.
|
|
16901
|
+
*
|
|
16902
|
+
* @param {string} cmakeListFile Path to the CMake-like build file
|
|
16903
|
+
* @param {string} pkgType PackageURL type to assign to extracted packages (e.g. `"generic"`)
|
|
16904
|
+
* @param {Object} options CLI options; may contain `projectGroup`, `projectName`, and `projectVersion`
|
|
16905
|
+
* @returns {{ parentComponent: Object, pkgList: Object[] }}
|
|
16906
|
+
*/
|
|
16496
16907
|
export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
16497
16908
|
let cmakeListData = readFileSync(cmakeListFile, { encoding: "utf-8" });
|
|
16498
16909
|
const pkgList = [];
|
|
@@ -16746,6 +17157,14 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
|
16746
17157
|
};
|
|
16747
17158
|
}
|
|
16748
17159
|
|
|
17160
|
+
/**
|
|
17161
|
+
* Find the OS package component that provides a given file, by searching the
|
|
17162
|
+
* `PkgProvides` property of each package in the OS package list.
|
|
17163
|
+
*
|
|
17164
|
+
* @param {string} afile Filename or path to look up (matched case-insensitively)
|
|
17165
|
+
* @param {Object[]} osPkgsList Array of OS package component objects to search
|
|
17166
|
+
* @returns {Object|undefined} The matching OS package component, or undefined if not found
|
|
17167
|
+
*/
|
|
16749
17168
|
export function getOSPackageForFile(afile, osPkgsList) {
|
|
16750
17169
|
for (const ospkg of osPkgsList) {
|
|
16751
17170
|
for (const props of ospkg.properties || []) {
|
|
@@ -17359,6 +17778,18 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
|
|
|
17359
17778
|
};
|
|
17360
17779
|
}
|
|
17361
17780
|
|
|
17781
|
+
/**
|
|
17782
|
+
* Enrich .NET package components with occurrence evidence and imported module/method
|
|
17783
|
+
* information from a dosai dependency slices file.
|
|
17784
|
+
*
|
|
17785
|
+
* Builds a mapping of DLL filenames to purls using the `PackageFiles` property of each
|
|
17786
|
+
* package, then reads the slices file to add occurrence locations, imported modules,
|
|
17787
|
+
* called methods, and assembly version information where available.
|
|
17788
|
+
*
|
|
17789
|
+
* @param {Object[]} pkgList Array of .NET package component objects to enrich
|
|
17790
|
+
* @param {string} slicesFile Path to the dosai dependency slices JSON file
|
|
17791
|
+
* @returns {Object[]} The enriched package list (same array, mutated in place)
|
|
17792
|
+
*/
|
|
17362
17793
|
export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
17363
17794
|
// We need two datastructures.
|
|
17364
17795
|
// dll to purl mapping from the pkgList
|
|
@@ -17793,7 +18224,7 @@ export function collectSharedLibs(
|
|
|
17793
18224
|
}
|
|
17794
18225
|
|
|
17795
18226
|
function collectAllLdConfs(basePath, ldConf, allLdConfDirs, libPaths) {
|
|
17796
|
-
if (ldConf &&
|
|
18227
|
+
if (ldConf && safeExistsSync(join(basePath, ldConf))) {
|
|
17797
18228
|
const ldConfData = readFileSync(join(basePath, ldConf), "utf-8");
|
|
17798
18229
|
for (let line of ldConfData.split("\n")) {
|
|
17799
18230
|
line = line.replace("\r", "").trim();
|
|
@@ -17979,6 +18410,14 @@ export function retrieveCdxgenVersion() {
|
|
|
17979
18410
|
return `\x1b[1mCycloneDX Generator ${packageJson.version}\x1b[0m\nRuntime: ${runtimeInfo.runtime}, Version: ${runtimeInfo.version}`;
|
|
17980
18411
|
}
|
|
17981
18412
|
|
|
18413
|
+
/**
|
|
18414
|
+
* Retrieve the version of the cdxgen plugins binary package from package.json.
|
|
18415
|
+
*
|
|
18416
|
+
* Reads the local package.json and searches the `optionalDependencies` for a package
|
|
18417
|
+
* whose name starts with `@cdxgen/cdxgen-plugins-bin`, returning its declared version.
|
|
18418
|
+
*
|
|
18419
|
+
* @returns {string|undefined} Version string of the plugins binary package, or undefined if not found
|
|
18420
|
+
*/
|
|
17982
18421
|
export function retrieveCdxgenPluginVersion() {
|
|
17983
18422
|
const packageJsonAsString = readFileSync(
|
|
17984
18423
|
join(dirNameStr, "package.json"),
|
|
@@ -18043,3 +18482,13 @@ export function splitCommandArgs(commandString) {
|
|
|
18043
18482
|
}
|
|
18044
18483
|
return args;
|
|
18045
18484
|
}
|
|
18485
|
+
|
|
18486
|
+
/**
|
|
18487
|
+
* Convert hyphenated strings to camel case.
|
|
18488
|
+
*
|
|
18489
|
+
* @param {String} str String to convert
|
|
18490
|
+
* @returns {String} camelCased string
|
|
18491
|
+
*/
|
|
18492
|
+
export function toCamel(str) {
|
|
18493
|
+
return str.replace(/-([a-z])/g, (_, g) => g.toUpperCase());
|
|
18494
|
+
}
|