@cyclonedx/cdxgen 12.1.5 → 12.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -39
- package/bin/cdxgen.js +175 -96
- package/bin/evinse.js +4 -4
- package/bin/repl.js +1 -1
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +327 -372
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +2 -14
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +423 -4
- package/lib/helpers/envcontext.js +18 -3
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +9 -0
- package/lib/helpers/utils.js +681 -406
- package/lib/helpers/utils.poku.js +55 -255
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +3 -9
- package/lib/parsers/npmrc.js +17 -13
- package/lib/parsers/npmrc.poku.js +41 -5
- package/lib/server/openapi.yaml +1 -1
- package/lib/server/server.js +40 -11
- package/lib/server/server.poku.js +123 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +197 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -8
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +10 -1
- package/types/lib/helpers/pythonutils.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +532 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +4 -1
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +21 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/lib/stages/pregen/env-audit.js +0 -34
- package/lib/stages/pregen/env-audit.poku.js +0 -290
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/lib/stages/pregen/env-audit.d.ts +0 -2
- package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
package/lib/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
|
}
|
|
@@ -303,6 +370,12 @@ export const PREFER_MAVEN_DEPS_TREE = !["false", "0"].includes(
|
|
|
303
370
|
process.env?.PREFER_MAVEN_DEPS_TREE,
|
|
304
371
|
);
|
|
305
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Determines whether license information should be fetched from remote sources,
|
|
375
|
+
* based on the FETCH_LICENSE environment variable.
|
|
376
|
+
*
|
|
377
|
+
* @returns {boolean} True if the FETCH_LICENSE env var is set to "true" or "1"
|
|
378
|
+
*/
|
|
306
379
|
export function shouldFetchLicense() {
|
|
307
380
|
return (
|
|
308
381
|
process.env.FETCH_LICENSE &&
|
|
@@ -310,6 +383,12 @@ export function shouldFetchLicense() {
|
|
|
310
383
|
);
|
|
311
384
|
}
|
|
312
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Determines whether VCS (version control system) information should be fetched
|
|
388
|
+
* for Go packages, based on the GO_FETCH_VCS environment variable.
|
|
389
|
+
*
|
|
390
|
+
* @returns {boolean} True if the GO_FETCH_VCS env var is set to "true" or "1"
|
|
391
|
+
*/
|
|
313
392
|
export function shouldFetchVCS() {
|
|
314
393
|
return (
|
|
315
394
|
process.env.GO_FETCH_VCS && ["true", "1"].includes(process.env.GO_FETCH_VCS)
|
|
@@ -336,6 +415,12 @@ const MAX_LICENSE_ID_LENGTH = 100;
|
|
|
336
415
|
|
|
337
416
|
export const JAVA_CMD = getJavaCommand();
|
|
338
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Returns the Java executable command to use, resolved in priority order:
|
|
420
|
+
* JAVA_CMD env var > JAVA_HOME/bin/java > "java".
|
|
421
|
+
*
|
|
422
|
+
* @returns {string} Path or name of the Java executable
|
|
423
|
+
*/
|
|
339
424
|
export function getJavaCommand() {
|
|
340
425
|
let javaCmd = "java";
|
|
341
426
|
if (process.env.JAVA_CMD) {
|
|
@@ -352,6 +437,12 @@ export function getJavaCommand() {
|
|
|
352
437
|
|
|
353
438
|
export const PYTHON_CMD = getPythonCommand();
|
|
354
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Returns the Python executable command to use, resolved in priority order:
|
|
442
|
+
* PYTHON_CMD env var > CONDA_PYTHON_EXE env var > "python".
|
|
443
|
+
*
|
|
444
|
+
* @returns {string} Path or name of the Python executable
|
|
445
|
+
*/
|
|
355
446
|
export function getPythonCommand() {
|
|
356
447
|
let pythonCmd = "python";
|
|
357
448
|
if (process.env.PYTHON_CMD) {
|
|
@@ -486,7 +577,6 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
486
577
|
"typescript",
|
|
487
578
|
"ts",
|
|
488
579
|
"tsx",
|
|
489
|
-
"vsix",
|
|
490
580
|
"yarn",
|
|
491
581
|
"rush",
|
|
492
582
|
],
|
|
@@ -581,6 +671,14 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
581
671
|
scala: ["scala", "scala3", "sbt", "mill"],
|
|
582
672
|
nix: ["nix", "nixos", "flake"],
|
|
583
673
|
caxa: ["caxa"],
|
|
674
|
+
"vscode-extension": [
|
|
675
|
+
"vscode-extension",
|
|
676
|
+
"vsix",
|
|
677
|
+
"vscode",
|
|
678
|
+
"openvsx",
|
|
679
|
+
"vscode-extensions",
|
|
680
|
+
"ide-extensions",
|
|
681
|
+
],
|
|
584
682
|
};
|
|
585
683
|
|
|
586
684
|
// Package manager aliases
|
|
@@ -1158,6 +1256,13 @@ export function readLicenseText(licenseFilepath, licenseContentType) {
|
|
|
1158
1256
|
return null;
|
|
1159
1257
|
}
|
|
1160
1258
|
|
|
1259
|
+
/**
|
|
1260
|
+
* Fetches license information for a list of Swift packages by querying the
|
|
1261
|
+
* GitHub repository license API for packages hosted on github.com.
|
|
1262
|
+
*
|
|
1263
|
+
* @param {Object[]} pkgList List of Swift package objects with optional repository.url fields
|
|
1264
|
+
* @returns {Promise<Object[]>} Resolved list of package objects, each augmented with a license field where available
|
|
1265
|
+
*/
|
|
1161
1266
|
export async function getSwiftPackageMetadata(pkgList) {
|
|
1162
1267
|
const cdepList = [];
|
|
1163
1268
|
for (const p of pkgList) {
|
|
@@ -1242,8 +1347,13 @@ export async function getNpmMetadata(pkgList) {
|
|
|
1242
1347
|
*
|
|
1243
1348
|
* @param {string} pkgJsonFile package.json file
|
|
1244
1349
|
* @param {boolean} simple Return a simpler representation of the component by skipping extended attributes and license fetch.
|
|
1350
|
+
* @param {boolean} securityProps Collect security-related properties
|
|
1245
1351
|
*/
|
|
1246
|
-
export async function parsePkgJson(
|
|
1352
|
+
export async function parsePkgJson(
|
|
1353
|
+
pkgJsonFile,
|
|
1354
|
+
simple = false,
|
|
1355
|
+
securityProps = false,
|
|
1356
|
+
) {
|
|
1247
1357
|
const pkgList = [];
|
|
1248
1358
|
if (safeExistsSync(pkgJsonFile)) {
|
|
1249
1359
|
try {
|
|
@@ -1306,6 +1416,107 @@ export async function parsePkgJson(pkgJsonFile, simple = false) {
|
|
|
1306
1416
|
},
|
|
1307
1417
|
};
|
|
1308
1418
|
}
|
|
1419
|
+
if (securityProps) {
|
|
1420
|
+
if (!apkg.properties) {
|
|
1421
|
+
apkg.properties = [];
|
|
1422
|
+
}
|
|
1423
|
+
// Track executable binaries (potential code execution vectors)
|
|
1424
|
+
if (pkgData.bin) {
|
|
1425
|
+
const binValue =
|
|
1426
|
+
typeof pkgData.bin === "object"
|
|
1427
|
+
? Object.keys(pkgData.bin).join(", ")
|
|
1428
|
+
: pkgData.bin;
|
|
1429
|
+
apkg.properties.push({
|
|
1430
|
+
name: "cdx:npm:bin",
|
|
1431
|
+
value: binValue,
|
|
1432
|
+
});
|
|
1433
|
+
apkg.properties.push({
|
|
1434
|
+
name: "cdx:npm:has_binary",
|
|
1435
|
+
value: "true",
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
// Track lifecycle scripts (preinstall, postinstall, etc. - code execution risk)
|
|
1439
|
+
if (pkgData.scripts && Object.keys(pkgData.scripts).length) {
|
|
1440
|
+
const scriptNames = Object.keys(pkgData.scripts).join(", ");
|
|
1441
|
+
apkg.properties.push({
|
|
1442
|
+
name: "cdx:npm:scripts",
|
|
1443
|
+
value: scriptNames,
|
|
1444
|
+
});
|
|
1445
|
+
// Flag high-risk scripts specifically
|
|
1446
|
+
const riskyScripts = [
|
|
1447
|
+
"preinstall",
|
|
1448
|
+
"install",
|
|
1449
|
+
"postinstall",
|
|
1450
|
+
"prepublish",
|
|
1451
|
+
"prepare",
|
|
1452
|
+
].filter((script) => pkgData.scripts[script]);
|
|
1453
|
+
if (riskyScripts.length) {
|
|
1454
|
+
apkg.properties.push({
|
|
1455
|
+
name: "cdx:npm:risky_scripts",
|
|
1456
|
+
value: riskyScripts.join(", "),
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
// Track platform/architecture constraints
|
|
1461
|
+
if (pkgData.cpu && Array.isArray(pkgData.cpu) && pkgData.cpu.length) {
|
|
1462
|
+
apkg.properties.push({
|
|
1463
|
+
name: "cdx:npm:cpu",
|
|
1464
|
+
value: pkgData.cpu.join(", "),
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
if (pkgData.os && Array.isArray(pkgData.os) && pkgData.os.length) {
|
|
1468
|
+
apkg.properties.push({
|
|
1469
|
+
name: "cdx:npm:os",
|
|
1470
|
+
value: pkgData.os.join(", "),
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
if (
|
|
1474
|
+
pkgData.libc &&
|
|
1475
|
+
Array.isArray(pkgData.libc) &&
|
|
1476
|
+
pkgData.libc.length
|
|
1477
|
+
) {
|
|
1478
|
+
apkg.properties.push({
|
|
1479
|
+
name: "cdx:npm:libc",
|
|
1480
|
+
value: pkgData.libc.join(", "),
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
// Track deprecation notices
|
|
1484
|
+
if (pkgData.deprecated) {
|
|
1485
|
+
apkg.properties.push({
|
|
1486
|
+
name: "cdx:npm:deprecation_notice",
|
|
1487
|
+
value: pkgData.deprecated,
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
// Track if package uses node-gyp (native C/C++ addons = higher risk)
|
|
1491
|
+
if (
|
|
1492
|
+
pkgData.gypfile === true ||
|
|
1493
|
+
pkgData.files?.some((f) => f.endsWith(".gyp") || f.endsWith(".gypi"))
|
|
1494
|
+
) {
|
|
1495
|
+
apkg.properties.push({
|
|
1496
|
+
name: "cdx:npm:gypfile",
|
|
1497
|
+
value: "true",
|
|
1498
|
+
});
|
|
1499
|
+
apkg.properties.push({
|
|
1500
|
+
name: "cdx:npm:native_addon",
|
|
1501
|
+
value: "true",
|
|
1502
|
+
});
|
|
1503
|
+
const nativeDeps = [
|
|
1504
|
+
"nan",
|
|
1505
|
+
"node-addon-api",
|
|
1506
|
+
"bindings",
|
|
1507
|
+
"node-gyp-build",
|
|
1508
|
+
];
|
|
1509
|
+
const foundNativeDeps = Object.keys(
|
|
1510
|
+
pkgData.dependencies || {},
|
|
1511
|
+
).filter((dep) => nativeDeps.includes(dep));
|
|
1512
|
+
if (foundNativeDeps.length) {
|
|
1513
|
+
apkg.properties.push({
|
|
1514
|
+
name: "cdx:npm:native_deps",
|
|
1515
|
+
value: foundNativeDeps.join(", "),
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1309
1520
|
pkgList.push(apkg);
|
|
1310
1521
|
} catch (_err) {
|
|
1311
1522
|
// continue regardless of error
|
|
@@ -1450,7 +1661,10 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1450
1661
|
name: "ResolvedUrl",
|
|
1451
1662
|
value: node.resolved,
|
|
1452
1663
|
});
|
|
1453
|
-
pkg.
|
|
1664
|
+
pkg.externalReferences.push({
|
|
1665
|
+
type: "distribution",
|
|
1666
|
+
url: node.resolved,
|
|
1667
|
+
});
|
|
1454
1668
|
}
|
|
1455
1669
|
}
|
|
1456
1670
|
if (node.location) {
|
|
@@ -1731,7 +1945,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1731
1945
|
if (!targetVersion || !targetName) {
|
|
1732
1946
|
if (pkgSpecVersionCache[`${edge.name}-${edge.spec}`]) {
|
|
1733
1947
|
targetVersion = pkgSpecVersionCache[`${edge.name}-${edge.spec}`];
|
|
1734
|
-
targetName = edge.name;
|
|
1948
|
+
targetName = edge.name.replace(/-cjs$/, "");
|
|
1735
1949
|
}
|
|
1736
1950
|
}
|
|
1737
1951
|
}
|
|
@@ -2774,6 +2988,13 @@ function findMatchingWorkspace(workspacePackages, packageName) {
|
|
|
2774
2988
|
);
|
|
2775
2989
|
}
|
|
2776
2990
|
|
|
2991
|
+
/**
|
|
2992
|
+
* Parses the workspaces field from a package.json file and returns the list of
|
|
2993
|
+
* workspace glob patterns. Handles both array and object (with packages key) formats.
|
|
2994
|
+
*
|
|
2995
|
+
* @param {string} packageJsonFile Path to the package.json file to parse
|
|
2996
|
+
* @returns {Object} Object with a packages array of workspace glob patterns, or an empty object on error
|
|
2997
|
+
*/
|
|
2777
2998
|
export function parseYarnWorkspace(packageJsonFile) {
|
|
2778
2999
|
try {
|
|
2779
3000
|
const packageData = JSON.parse(readFileSync(packageJsonFile, "utf-8"));
|
|
@@ -2872,42 +3093,28 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
|
2872
3093
|
if (!pkgList?.length || !lockFilePath) {
|
|
2873
3094
|
return pkgList;
|
|
2874
3095
|
}
|
|
2875
|
-
|
|
2876
3096
|
const baseDir = dirname(lockFilePath);
|
|
2877
3097
|
const nodeModulesDir = join(baseDir, "node_modules");
|
|
2878
|
-
|
|
2879
|
-
// Only proceed if node_modules exists
|
|
2880
3098
|
if (!safeExistsSync(nodeModulesDir)) {
|
|
2881
3099
|
return pkgList;
|
|
2882
3100
|
}
|
|
2883
|
-
|
|
2884
3101
|
if (DEBUG_MODE) {
|
|
2885
3102
|
console.log(
|
|
2886
3103
|
`Metadata for ${pkgList.length} pnpm packages using local node_modules at ${nodeModulesDir}`,
|
|
2887
3104
|
);
|
|
2888
3105
|
}
|
|
2889
|
-
|
|
2890
3106
|
let enhancedCount = 0;
|
|
2891
3107
|
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
3108
|
const packagePath = findPnpmPackagePath(baseDir, pkg.name, pkg.version);
|
|
2899
3109
|
if (!packagePath) {
|
|
2900
3110
|
continue;
|
|
2901
3111
|
}
|
|
2902
|
-
|
|
2903
3112
|
const packageJsonPath = join(packagePath, "package.json");
|
|
2904
3113
|
if (!safeExistsSync(packageJsonPath)) {
|
|
2905
3114
|
continue;
|
|
2906
3115
|
}
|
|
2907
|
-
|
|
2908
3116
|
try {
|
|
2909
|
-
|
|
2910
|
-
const localPkgList = await parsePkgJson(packageJsonPath, true);
|
|
3117
|
+
const localPkgList = await parsePkgJson(packageJsonPath, true, true);
|
|
2911
3118
|
if (localPkgList && localPkgList.length === 1) {
|
|
2912
3119
|
const localMetadata = localPkgList[0];
|
|
2913
3120
|
if (localMetadata && Object.keys(localMetadata).length) {
|
|
@@ -2926,16 +3133,27 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
|
2926
3133
|
if (!pkg.repository && localMetadata.repository) {
|
|
2927
3134
|
pkg.repository = localMetadata.repository;
|
|
2928
3135
|
}
|
|
2929
|
-
|
|
2930
|
-
// Add a property to track that we enhanced from local node_modules
|
|
2931
3136
|
if (!pkg.properties) {
|
|
2932
3137
|
pkg.properties = [];
|
|
2933
3138
|
}
|
|
3139
|
+
if (localMetadata?.properties?.length) {
|
|
3140
|
+
const seenProperties = new Set(
|
|
3141
|
+
pkg.properties.map(
|
|
3142
|
+
(prop) => `${String(prop?.name)}\u0000${String(prop?.value)}`,
|
|
3143
|
+
),
|
|
3144
|
+
);
|
|
3145
|
+
for (const prop of localMetadata.properties) {
|
|
3146
|
+
const propertyKey = `${String(prop?.name)}\u0000${String(prop?.value)}`;
|
|
3147
|
+
if (!seenProperties.has(propertyKey)) {
|
|
3148
|
+
pkg.properties.push(prop);
|
|
3149
|
+
seenProperties.add(propertyKey);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
2934
3153
|
pkg.properties.push({
|
|
2935
3154
|
name: "LocalNodeModulesPath",
|
|
2936
3155
|
value: packagePath,
|
|
2937
3156
|
});
|
|
2938
|
-
|
|
2939
3157
|
enhancedCount++;
|
|
2940
3158
|
}
|
|
2941
3159
|
}
|
|
@@ -2949,13 +3167,11 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
|
2949
3167
|
}
|
|
2950
3168
|
}
|
|
2951
3169
|
}
|
|
2952
|
-
|
|
2953
3170
|
if (DEBUG_MODE && enhancedCount > 0) {
|
|
2954
3171
|
console.log(
|
|
2955
3172
|
`Enhanced metadata for ${enhancedCount} packages from local node_modules`,
|
|
2956
3173
|
);
|
|
2957
3174
|
}
|
|
2958
|
-
|
|
2959
3175
|
return pkgList;
|
|
2960
3176
|
}
|
|
2961
3177
|
|
|
@@ -3018,10 +3234,18 @@ export async function parsePnpmLock(
|
|
|
3018
3234
|
}
|
|
3019
3235
|
if (safeExistsSync(pnpmLock)) {
|
|
3020
3236
|
const lockData = readFileSync(pnpmLock, "utf8");
|
|
3021
|
-
|
|
3237
|
+
let yamlObj = parseAllDocuments(lockData);
|
|
3022
3238
|
if (!yamlObj) {
|
|
3023
3239
|
return {};
|
|
3024
3240
|
}
|
|
3241
|
+
if (Array.isArray(yamlObj)) {
|
|
3242
|
+
try {
|
|
3243
|
+
yamlObj = yamlObj[yamlObj.length - 1].toJS();
|
|
3244
|
+
} catch (_e) {
|
|
3245
|
+
console.log(`Unable to parse the pnpm lock file ${pnpmLock}.`);
|
|
3246
|
+
return {};
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3025
3249
|
lockfileVersion = yamlObj.lockfileVersion;
|
|
3026
3250
|
try {
|
|
3027
3251
|
lockfileVersion = Number.parseFloat(lockfileVersion, 10);
|
|
@@ -3320,6 +3544,7 @@ export async function parsePnpmLock(
|
|
|
3320
3544
|
packages[fullName]?.resolution ||
|
|
3321
3545
|
snapshots[fullName]?.resolution;
|
|
3322
3546
|
const integrity = resolution?.integrity;
|
|
3547
|
+
const tarball = resolution?.tarball;
|
|
3323
3548
|
const cpu =
|
|
3324
3549
|
packages[pkgKeys[k]]?.cpu ||
|
|
3325
3550
|
snapshots[pkgKeys[k]]?.cpu ||
|
|
@@ -3538,10 +3763,10 @@ export async function parsePnpmLock(
|
|
|
3538
3763
|
value: pnpmLock,
|
|
3539
3764
|
},
|
|
3540
3765
|
];
|
|
3541
|
-
if (hasBin
|
|
3766
|
+
if (hasBin) {
|
|
3542
3767
|
properties.push({
|
|
3543
3768
|
name: "cdx:npm:has_binary",
|
|
3544
|
-
value:
|
|
3769
|
+
value: "true",
|
|
3545
3770
|
});
|
|
3546
3771
|
}
|
|
3547
3772
|
if (deprecatedMessage) {
|
|
@@ -3554,7 +3779,7 @@ export async function parsePnpmLock(
|
|
|
3554
3779
|
Object.entries(binary_metadata).forEach(([key, value]) => {
|
|
3555
3780
|
if (!value) return;
|
|
3556
3781
|
properties.push({
|
|
3557
|
-
name: `cdx:
|
|
3782
|
+
name: `cdx:npm:${key}`,
|
|
3558
3783
|
value: Array.isArray(value) ? value.join(", ") : value,
|
|
3559
3784
|
});
|
|
3560
3785
|
});
|
|
@@ -3652,6 +3877,14 @@ export async function parsePnpmLock(
|
|
|
3652
3877
|
},
|
|
3653
3878
|
},
|
|
3654
3879
|
};
|
|
3880
|
+
if (tarball) {
|
|
3881
|
+
thePkg.externalReferences = [
|
|
3882
|
+
{
|
|
3883
|
+
type: "distribution",
|
|
3884
|
+
url: tarball,
|
|
3885
|
+
},
|
|
3886
|
+
];
|
|
3887
|
+
}
|
|
3655
3888
|
// Don't add internal workspace packages to the components list
|
|
3656
3889
|
if (thePkg.type !== "application") {
|
|
3657
3890
|
pkgList.push(thePkg);
|
|
@@ -4685,6 +4918,15 @@ export function parseLeinDep(rawOutput) {
|
|
|
4685
4918
|
return [];
|
|
4686
4919
|
}
|
|
4687
4920
|
|
|
4921
|
+
/**
|
|
4922
|
+
* Recursively walks a parsed EDN map node produced by the Leiningen dependency
|
|
4923
|
+
* tree and collects unique dependency entries into the deps array.
|
|
4924
|
+
*
|
|
4925
|
+
* @param {Object} node Parsed EDN node (expected to have a "map" property)
|
|
4926
|
+
* @param {Object} keys_cache Cache object used to deduplicate entries by group-name-version key
|
|
4927
|
+
* @param {Object[]} deps Accumulator array of dependency objects with group, name, and version fields
|
|
4928
|
+
* @returns {Object[]} The populated deps array
|
|
4929
|
+
*/
|
|
4688
4930
|
export function parseLeinMap(node, keys_cache, deps) {
|
|
4689
4931
|
if (node["map"]) {
|
|
4690
4932
|
for (const n of node["map"]) {
|
|
@@ -7302,6 +7544,16 @@ async function getGoPkgVCSUrl(group, name) {
|
|
|
7302
7544
|
return undefined;
|
|
7303
7545
|
}
|
|
7304
7546
|
|
|
7547
|
+
/**
|
|
7548
|
+
* Builds a Go package component object containing purl, bom-ref, integrity hash,
|
|
7549
|
+
* and optionally license and VCS external reference information.
|
|
7550
|
+
*
|
|
7551
|
+
* @param {string} group Package group (module path prefix, may be empty)
|
|
7552
|
+
* @param {string} name Package name (full module path when group is empty)
|
|
7553
|
+
* @param {string} version Package version string
|
|
7554
|
+
* @param {string} hash Integrity hash (e.g. "sha256-…"), used as _integrity
|
|
7555
|
+
* @returns {Promise<Object>} Component object ready for inclusion in a BOM package list
|
|
7556
|
+
*/
|
|
7305
7557
|
export async function getGoPkgComponent(group, name, version, hash) {
|
|
7306
7558
|
let license;
|
|
7307
7559
|
if (shouldFetchLicense()) {
|
|
@@ -7485,6 +7737,15 @@ export async function parseGoModData(goModData, gosumMap) {
|
|
|
7485
7737
|
};
|
|
7486
7738
|
}
|
|
7487
7739
|
|
|
7740
|
+
/**
|
|
7741
|
+
* Parses a Go modules text file (e.g. vendor/modules.txt) and returns a list of
|
|
7742
|
+
* Go package components. Cross-references the go.sum map for integrity hashes and
|
|
7743
|
+
* sets scope and confidence based on hash availability.
|
|
7744
|
+
*
|
|
7745
|
+
* @param {string} txtFile Path to the modules.txt file
|
|
7746
|
+
* @param {Object} gosumMap Map of "module@version" keys to sha256 hash values from go.sum
|
|
7747
|
+
* @returns {Promise<Object[]>} List of Go package component objects with evidence
|
|
7748
|
+
*/
|
|
7488
7749
|
export async function parseGoModulesTxt(txtFile, gosumMap) {
|
|
7489
7750
|
const pkgList = [];
|
|
7490
7751
|
const txtData = readFileSync(txtFile, { encoding: "utf-8" });
|
|
@@ -7888,6 +8149,14 @@ export async function parseGosumData(gosumData) {
|
|
|
7888
8149
|
return pkgList;
|
|
7889
8150
|
}
|
|
7890
8151
|
|
|
8152
|
+
/**
|
|
8153
|
+
* Parses the contents of a Gopkg.lock or Gopkg.toml file (dep tool format) and
|
|
8154
|
+
* returns a list of Go package components. Optionally fetches license information
|
|
8155
|
+
* for each package when FETCH_LICENSE is enabled.
|
|
8156
|
+
*
|
|
8157
|
+
* @param {string} gopkgData Raw string contents of the Gopkg lock/toml file
|
|
8158
|
+
* @returns {Promise<Object[]>} List of Go package component objects
|
|
8159
|
+
*/
|
|
7891
8160
|
export async function parseGopkgData(gopkgData) {
|
|
7892
8161
|
const pkgList = [];
|
|
7893
8162
|
if (!gopkgData) {
|
|
@@ -7937,6 +8206,13 @@ export async function parseGopkgData(gopkgData) {
|
|
|
7937
8206
|
return pkgList;
|
|
7938
8207
|
}
|
|
7939
8208
|
|
|
8209
|
+
/**
|
|
8210
|
+
* Parses the output of `go version -m` (build info) and returns a list of Go
|
|
8211
|
+
* package components for each "dep" line, including name, version, and integrity hash.
|
|
8212
|
+
*
|
|
8213
|
+
* @param {string} buildInfoData Raw string output from `go version -m`
|
|
8214
|
+
* @returns {Promise<Object[]>} List of Go package component objects
|
|
8215
|
+
*/
|
|
7940
8216
|
export async function parseGoVersionData(buildInfoData) {
|
|
7941
8217
|
const pkgList = [];
|
|
7942
8218
|
if (!buildInfoData) {
|
|
@@ -9263,6 +9539,13 @@ export async function parseCargoData(
|
|
|
9263
9539
|
return pkgList;
|
|
9264
9540
|
}
|
|
9265
9541
|
|
|
9542
|
+
/**
|
|
9543
|
+
* Parses a Cargo.lock file's TOML data and returns a flat dependency graph as an
|
|
9544
|
+
* array of objects mapping each package purl to the purls it directly depends on.
|
|
9545
|
+
*
|
|
9546
|
+
* @param {string} cargoLockData Raw TOML string contents of a Cargo.lock file
|
|
9547
|
+
* @returns {Object[]} Array of dependency relationship objects with ref and dependsOn fields
|
|
9548
|
+
*/
|
|
9266
9549
|
export function parseCargoDependencyData(cargoLockData) {
|
|
9267
9550
|
const purlFromPackageInfo = (pkg) =>
|
|
9268
9551
|
decodeURIComponent(
|
|
@@ -9322,6 +9605,14 @@ export function parseCargoDependencyData(cargoLockData) {
|
|
|
9322
9605
|
return result;
|
|
9323
9606
|
}
|
|
9324
9607
|
|
|
9608
|
+
/**
|
|
9609
|
+
* Parses tab-separated cargo-auditable binary metadata output and returns a list
|
|
9610
|
+
* of Rust package components. Optionally fetches crates.io metadata when
|
|
9611
|
+
* FETCH_LICENSE is enabled.
|
|
9612
|
+
*
|
|
9613
|
+
* @param {string} cargoData Tab-separated string output from cargo-auditable or similar tool
|
|
9614
|
+
* @returns {Promise<Object[]>} List of Rust package component objects with group, name, and version
|
|
9615
|
+
*/
|
|
9325
9616
|
export async function parseCargoAuditableData(cargoData) {
|
|
9326
9617
|
const pkgList = [];
|
|
9327
9618
|
if (!cargoData) {
|
|
@@ -9424,6 +9715,13 @@ export async function parsePubLockData(pubLockData, lockFile) {
|
|
|
9424
9715
|
return { rootList, pkgList };
|
|
9425
9716
|
}
|
|
9426
9717
|
|
|
9718
|
+
/**
|
|
9719
|
+
* Parses a Dart pub package's pubspec.yaml content and returns a list containing
|
|
9720
|
+
* a single component object with name, description, version, homepage, and purl.
|
|
9721
|
+
*
|
|
9722
|
+
* @param {string} pubYamlData Raw YAML string contents of a pubspec.yaml file
|
|
9723
|
+
* @returns {Object[]} List containing a single Dart package component object
|
|
9724
|
+
*/
|
|
9427
9725
|
export function parsePubYamlData(pubYamlData) {
|
|
9428
9726
|
const pkgList = [];
|
|
9429
9727
|
let yamlObj;
|
|
@@ -9450,6 +9748,14 @@ export function parsePubYamlData(pubYamlData) {
|
|
|
9450
9748
|
return pkgList;
|
|
9451
9749
|
}
|
|
9452
9750
|
|
|
9751
|
+
/**
|
|
9752
|
+
* Parses Helm chart YAML data (Chart.yaml or repository index.yaml) and returns
|
|
9753
|
+
* a list of Helm chart component objects including the chart itself and any
|
|
9754
|
+
* declared dependencies or index entries.
|
|
9755
|
+
*
|
|
9756
|
+
* @param {string} helmData Raw YAML string contents of a Helm Chart.yaml or index.yaml file
|
|
9757
|
+
* @returns {Object[]} List of Helm chart component objects with name, version, and optional homepage/repository
|
|
9758
|
+
*/
|
|
9453
9759
|
export function parseHelmYamlData(helmData) {
|
|
9454
9760
|
const pkgList = [];
|
|
9455
9761
|
let yamlObj;
|
|
@@ -9515,6 +9821,17 @@ export function parseHelmYamlData(helmData) {
|
|
|
9515
9821
|
return pkgList;
|
|
9516
9822
|
}
|
|
9517
9823
|
|
|
9824
|
+
/**
|
|
9825
|
+
* Recursively walks a parsed YAML/JSON object structure to find container image
|
|
9826
|
+
* references stored under common keys (image, repository, dockerImage, etc.) and
|
|
9827
|
+
* appends discovered image and service entries to pkgList while tracking seen
|
|
9828
|
+
* images in imgList to avoid duplicates.
|
|
9829
|
+
*
|
|
9830
|
+
* @param {Object|Array|string} keyValueObj The object, array, or string node to inspect
|
|
9831
|
+
* @param {Object[]} pkgList Accumulator array that receives {image} and {service} entries
|
|
9832
|
+
* @param {string[]} imgList Accumulator array of image name strings already seen
|
|
9833
|
+
* @returns {string[]} The updated imgList
|
|
9834
|
+
*/
|
|
9518
9835
|
export function recurseImageNameLookup(keyValueObj, pkgList, imgList) {
|
|
9519
9836
|
if (typeof keyValueObj === "string" || keyValueObj instanceof String) {
|
|
9520
9837
|
return imgList;
|
|
@@ -9600,6 +9917,14 @@ function substituteBuildArgs(statement, buildArgs) {
|
|
|
9600
9917
|
return statement;
|
|
9601
9918
|
}
|
|
9602
9919
|
|
|
9920
|
+
/**
|
|
9921
|
+
* Parses the contents of a Dockerfile or Containerfile and returns a list of
|
|
9922
|
+
* base image objects referenced by FROM instructions, substituting ARG default
|
|
9923
|
+
* values where possible and skipping multi-stage build alias references.
|
|
9924
|
+
*
|
|
9925
|
+
* @param {string} fileContents Raw string contents of the Dockerfile/Containerfile
|
|
9926
|
+
* @returns {Object[]} Array of objects with an image property for each unique base image
|
|
9927
|
+
*/
|
|
9603
9928
|
export function parseContainerFile(fileContents) {
|
|
9604
9929
|
const buildArgs = new Map();
|
|
9605
9930
|
const imagesSet = new Set();
|
|
@@ -9667,6 +9992,13 @@ export function parseContainerFile(fileContents) {
|
|
|
9667
9992
|
});
|
|
9668
9993
|
}
|
|
9669
9994
|
|
|
9995
|
+
/**
|
|
9996
|
+
* Parses a Bitbucket Pipelines YAML file and extracts all Docker image references
|
|
9997
|
+
* used as build environments and pipe references (docker:// pipes are normalized).
|
|
9998
|
+
*
|
|
9999
|
+
* @param {string} fileContents Raw string contents of the bitbucket-pipelines.yml file
|
|
10000
|
+
* @returns {Object[]} Array of objects with an image property for each referenced image or pipe
|
|
10001
|
+
*/
|
|
9670
10002
|
export function parseBitbucketPipelinesFile(fileContents) {
|
|
9671
10003
|
const imgList = [];
|
|
9672
10004
|
|
|
@@ -9728,6 +10060,14 @@ export function parseBitbucketPipelinesFile(fileContents) {
|
|
|
9728
10060
|
return imgList;
|
|
9729
10061
|
}
|
|
9730
10062
|
|
|
10063
|
+
/**
|
|
10064
|
+
* Parses container specification data such as Docker Compose files, Kubernetes
|
|
10065
|
+
* manifests, Tekton tasks, Skaffold configs, or Kustomize overlays (YAML, possibly
|
|
10066
|
+
* multi-document) and returns a list of image, service, and OCI spec entries.
|
|
10067
|
+
*
|
|
10068
|
+
* @param {string} dcData Raw YAML string contents of the container spec file
|
|
10069
|
+
* @returns {Object[]} Array of objects with image, service, or ociSpec properties
|
|
10070
|
+
*/
|
|
9731
10071
|
export function parseContainerSpecData(dcData) {
|
|
9732
10072
|
const pkgList = [];
|
|
9733
10073
|
const imgList = [];
|
|
@@ -9795,6 +10135,14 @@ export function parseContainerSpecData(dcData) {
|
|
|
9795
10135
|
return pkgList;
|
|
9796
10136
|
}
|
|
9797
10137
|
|
|
10138
|
+
/**
|
|
10139
|
+
* Identifies the data flow direction of a Privado processing object based on its
|
|
10140
|
+
* sinkId value: "write" sinks map to "inbound", "read" sinks to "outbound", and
|
|
10141
|
+
* HTTP/gRPC sinks to "bi-directional".
|
|
10142
|
+
*
|
|
10143
|
+
* @param {Object} processingObj Privado processing object, expected to have a sinkId property
|
|
10144
|
+
* @returns {string} Flow direction string: "inbound", "outbound", "bi-directional", or "unknown"
|
|
10145
|
+
*/
|
|
9798
10146
|
export function identifyFlow(processingObj) {
|
|
9799
10147
|
let flow = "unknown";
|
|
9800
10148
|
if (processingObj.sinkId) {
|
|
@@ -9821,6 +10169,14 @@ function convertProcessing(processing_list) {
|
|
|
9821
10169
|
return data_list;
|
|
9822
10170
|
}
|
|
9823
10171
|
|
|
10172
|
+
/**
|
|
10173
|
+
* Parses a Privado data flow JSON file and returns a list of service objects
|
|
10174
|
+
* enriched with data classifications, endpoints, trust-boundary flag, violations,
|
|
10175
|
+
* and git metadata properties extracted from the scan result.
|
|
10176
|
+
*
|
|
10177
|
+
* @param {string} f Path to the Privado scan result JSON file
|
|
10178
|
+
* @returns {Object[]} List of service component objects suitable for a SaaSBOM
|
|
10179
|
+
*/
|
|
9824
10180
|
export function parsePrivadoFile(f) {
|
|
9825
10181
|
const pData = readFileSync(f, { encoding: "utf-8" });
|
|
9826
10182
|
const servlist = [];
|
|
@@ -9902,6 +10258,15 @@ export function parsePrivadoFile(f) {
|
|
|
9902
10258
|
return servlist;
|
|
9903
10259
|
}
|
|
9904
10260
|
|
|
10261
|
+
/**
|
|
10262
|
+
* Parses an OpenAPI specification (JSON or YAML string) and returns a list
|
|
10263
|
+
* containing a single service object with name, version, endpoints, and
|
|
10264
|
+
* authentication flag derived from the spec's info, servers, paths, and
|
|
10265
|
+
* securitySchemes sections.
|
|
10266
|
+
*
|
|
10267
|
+
* @param {string} oaData Raw JSON or YAML string contents of an OpenAPI specification
|
|
10268
|
+
* @returns {Object[]} List containing a single service component object
|
|
10269
|
+
*/
|
|
9905
10270
|
export function parseOpenapiSpecData(oaData) {
|
|
9906
10271
|
const servlist = [];
|
|
9907
10272
|
if (!oaData) {
|
|
@@ -9954,6 +10319,13 @@ export function parseOpenapiSpecData(oaData) {
|
|
|
9954
10319
|
return servlist;
|
|
9955
10320
|
}
|
|
9956
10321
|
|
|
10322
|
+
/**
|
|
10323
|
+
* Parses Haskell Cabal freeze file content and extracts package name and version
|
|
10324
|
+
* pairs from constraint lines (lines containing " ==").
|
|
10325
|
+
*
|
|
10326
|
+
* @param {string} cabalData Raw string contents of a Cabal freeze file
|
|
10327
|
+
* @returns {Object[]} List of package objects with name and version fields
|
|
10328
|
+
*/
|
|
9957
10329
|
export function parseCabalData(cabalData) {
|
|
9958
10330
|
const pkgList = [];
|
|
9959
10331
|
if (!cabalData) {
|
|
@@ -9982,6 +10354,13 @@ export function parseCabalData(cabalData) {
|
|
|
9982
10354
|
return pkgList;
|
|
9983
10355
|
}
|
|
9984
10356
|
|
|
10357
|
+
/**
|
|
10358
|
+
* Parses an Elixir mix.lock file and extracts Hex package name and version pairs
|
|
10359
|
+
* from lines containing ":hex".
|
|
10360
|
+
*
|
|
10361
|
+
* @param {string} mixData Raw string contents of a mix.lock file
|
|
10362
|
+
* @returns {Object[]} List of package objects with name and version fields
|
|
10363
|
+
*/
|
|
9985
10364
|
export function parseMixLockData(mixData) {
|
|
9986
10365
|
const pkgList = [];
|
|
9987
10366
|
if (!mixData) {
|
|
@@ -10009,376 +10388,27 @@ export function parseMixLockData(mixData) {
|
|
|
10009
10388
|
return pkgList;
|
|
10010
10389
|
}
|
|
10011
10390
|
|
|
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
10391
|
/**
|
|
10286
|
-
*
|
|
10392
|
+
* Parses a GitHub Actions workflow YAML file and returns a list of action
|
|
10393
|
+
* components for each step that uses an external action (steps with a "uses"
|
|
10394
|
+
* field). Each component captures the action name, group, version/commit SHA,
|
|
10395
|
+
* version pinning type, job context (runner, permissions, environment), and
|
|
10396
|
+
* workflow-level metadata (triggers, concurrency, write permissions).
|
|
10287
10397
|
*
|
|
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
|
|
10398
|
+
* @param {string} f Path to the GitHub Actions workflow YAML file
|
|
10399
|
+
* @returns {Object[]} List of action component objects with purl, properties, and evidence
|
|
10323
10400
|
*/
|
|
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";
|
|
10401
|
+
export function parseGitHubWorkflowData(f) {
|
|
10402
|
+
const { components } = parseWorkflowFile(f);
|
|
10403
|
+
return components.filter((c) => c.scope === "required");
|
|
10342
10404
|
}
|
|
10343
10405
|
|
|
10344
10406
|
/**
|
|
10345
|
-
*
|
|
10407
|
+
* Parse Google Cloud Build YAML data and extract container image steps as packages.
|
|
10408
|
+
*
|
|
10409
|
+
* @param {string} cbwData Raw YAML string of a Cloud Build configuration file
|
|
10410
|
+
* @returns {Object[]} Array of package objects parsed from the build steps
|
|
10346
10411
|
*/
|
|
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
10412
|
export function parseCloudBuildData(cbwData) {
|
|
10383
10413
|
const pkgList = [];
|
|
10384
10414
|
const keys_cache = {};
|
|
@@ -10455,6 +10485,16 @@ function untilFirst(separator, inputStr) {
|
|
|
10455
10485
|
];
|
|
10456
10486
|
}
|
|
10457
10487
|
|
|
10488
|
+
/**
|
|
10489
|
+
* Map a Conan package reference string to a PackageURL string, name, and version.
|
|
10490
|
+
*
|
|
10491
|
+
* Parses a full Conan package reference of the form
|
|
10492
|
+
* `name/version@user/channel#recipe_revision:package_id#package_revision`
|
|
10493
|
+
* and returns the equivalent purl string together with the extracted name and version.
|
|
10494
|
+
*
|
|
10495
|
+
* @param {string} conanPkgRef Conan package reference string
|
|
10496
|
+
* @returns {Array} Tuple of [purlString, name, version], or [null, null, null] on parse failure
|
|
10497
|
+
*/
|
|
10458
10498
|
export function mapConanPkgRefToPurlStringAndNameAndVersion(conanPkgRef) {
|
|
10459
10499
|
// A full Conan package reference may be composed of the following segments:
|
|
10460
10500
|
// conanPkgRef = "name/version@user/channel#recipe_revision:package_id#package_revision"
|
|
@@ -10580,6 +10620,16 @@ export function mapConanPkgRefToPurlStringAndNameAndVersion(conanPkgRef) {
|
|
|
10580
10620
|
return [purl, info.name, info.version];
|
|
10581
10621
|
}
|
|
10582
10622
|
|
|
10623
|
+
/**
|
|
10624
|
+
* Parse Conan lock file data (conan.lock) and return the package list, dependency map,
|
|
10625
|
+
* and parent component dependencies.
|
|
10626
|
+
*
|
|
10627
|
+
* Supports both the legacy `graph_lock.nodes` format (Conan 1.x) and the newer
|
|
10628
|
+
* `requires` format (Conan 2.x).
|
|
10629
|
+
*
|
|
10630
|
+
* @param {string} conanLockData Raw JSON string of the Conan lock file
|
|
10631
|
+
* @returns {{ pkgList: Object[], dependencies: Object, parentComponentDependencies: string[] }}
|
|
10632
|
+
*/
|
|
10583
10633
|
export function parseConanLockData(conanLockData) {
|
|
10584
10634
|
const pkgList = [];
|
|
10585
10635
|
const dependencies = {};
|
|
@@ -10663,6 +10713,12 @@ export function parseConanLockData(conanLockData) {
|
|
|
10663
10713
|
return { pkgList, dependencies, parentComponentDependencies };
|
|
10664
10714
|
}
|
|
10665
10715
|
|
|
10716
|
+
/**
|
|
10717
|
+
* Parse a Conan conanfile.txt and extract required and optional packages.
|
|
10718
|
+
*
|
|
10719
|
+
* @param {string} conanData Raw text contents of a conanfile.txt
|
|
10720
|
+
* @returns {Object[]} Array of package objects with purl, name, version, and scope
|
|
10721
|
+
*/
|
|
10666
10722
|
export function parseConanData(conanData) {
|
|
10667
10723
|
const pkgList = [];
|
|
10668
10724
|
if (!conanData) {
|
|
@@ -10698,6 +10754,12 @@ export function parseConanData(conanData) {
|
|
|
10698
10754
|
return pkgList;
|
|
10699
10755
|
}
|
|
10700
10756
|
|
|
10757
|
+
/**
|
|
10758
|
+
* Parse Leiningen project.clj data and extract dependency packages.
|
|
10759
|
+
*
|
|
10760
|
+
* @param {string} leinData Raw text contents of a Leiningen project.clj file
|
|
10761
|
+
* @returns {Object[]} Array of package objects with group, name, and version
|
|
10762
|
+
*/
|
|
10701
10763
|
export function parseLeiningenData(leinData) {
|
|
10702
10764
|
const pkgList = [];
|
|
10703
10765
|
if (!leinData) {
|
|
@@ -10732,6 +10794,14 @@ export function parseLeiningenData(leinData) {
|
|
|
10732
10794
|
return pkgList;
|
|
10733
10795
|
}
|
|
10734
10796
|
|
|
10797
|
+
/**
|
|
10798
|
+
* Parse EDN (Extensible Data Notation) deps.edn data and extract dependency packages.
|
|
10799
|
+
*
|
|
10800
|
+
* Handles Clojure deps.edn files, extracting packages listed under the `:deps` key.
|
|
10801
|
+
*
|
|
10802
|
+
* @param {string} rawEdnData Raw EDN text contents of a deps.edn file
|
|
10803
|
+
* @returns {Object[]} Array of package objects with group, name, and version
|
|
10804
|
+
*/
|
|
10735
10805
|
export function parseEdnData(rawEdnData) {
|
|
10736
10806
|
const pkgList = [];
|
|
10737
10807
|
if (!rawEdnData) {
|
|
@@ -10811,7 +10881,7 @@ export function parseFlakeNix(flakeNixFile) {
|
|
|
10811
10881
|
const pkgList = [];
|
|
10812
10882
|
const dependencies = [];
|
|
10813
10883
|
|
|
10814
|
-
if (!
|
|
10884
|
+
if (!safeExistsSync(flakeNixFile)) {
|
|
10815
10885
|
return { pkgList, dependencies };
|
|
10816
10886
|
}
|
|
10817
10887
|
|
|
@@ -10896,7 +10966,7 @@ export function parseFlakeLock(flakeLockFile) {
|
|
|
10896
10966
|
const pkgList = [];
|
|
10897
10967
|
const dependencies = [];
|
|
10898
10968
|
|
|
10899
|
-
if (!
|
|
10969
|
+
if (!safeExistsSync(flakeLockFile)) {
|
|
10900
10970
|
return { pkgList, dependencies };
|
|
10901
10971
|
}
|
|
10902
10972
|
|
|
@@ -11178,6 +11248,13 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
11178
11248
|
};
|
|
11179
11249
|
}
|
|
11180
11250
|
|
|
11251
|
+
/**
|
|
11252
|
+
* Parse a C# packages.config XML file and return a list of NuGet package components.
|
|
11253
|
+
*
|
|
11254
|
+
* @param {string} pkgData Raw XML string of a packages.config file
|
|
11255
|
+
* @param {string} pkgFile Path to the packages.config file, used for evidence properties
|
|
11256
|
+
* @returns {Object[]} Array of NuGet package objects with purl, name, and version
|
|
11257
|
+
*/
|
|
11181
11258
|
export function parseCsPkgData(pkgData, pkgFile) {
|
|
11182
11259
|
const pkgList = [];
|
|
11183
11260
|
if (!pkgData) {
|
|
@@ -11710,6 +11787,17 @@ export function parseCsProjData(
|
|
|
11710
11787
|
};
|
|
11711
11788
|
}
|
|
11712
11789
|
|
|
11790
|
+
/**
|
|
11791
|
+
* Parse a .NET project.assets.json file and return the package list and dependency tree.
|
|
11792
|
+
*
|
|
11793
|
+
* Extracts NuGet packages and their transitive dependency relationships from the
|
|
11794
|
+
* `libraries` and `targets` sections of a project.assets.json file produced by
|
|
11795
|
+
* the .NET restore process.
|
|
11796
|
+
*
|
|
11797
|
+
* @param {string} csProjData Raw JSON string of the project.assets.json file
|
|
11798
|
+
* @param {string} assetsJsonFile Path to the project.assets.json file, used for evidence properties
|
|
11799
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
11800
|
+
*/
|
|
11713
11801
|
export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
11714
11802
|
// extract name, operator, version from .NET package representation
|
|
11715
11803
|
// like "NLog >= 4.5.0"
|
|
@@ -11960,6 +12048,14 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
11960
12048
|
};
|
|
11961
12049
|
}
|
|
11962
12050
|
|
|
12051
|
+
/**
|
|
12052
|
+
* Parse a .NET packages.lock.json file and return the package list, dependency tree,
|
|
12053
|
+
* and list of direct/root dependencies.
|
|
12054
|
+
*
|
|
12055
|
+
* @param {string} csLockData Raw JSON string of the packages.lock.json file
|
|
12056
|
+
* @param {string} pkgLockFile Path to the packages.lock.json file, used for evidence properties
|
|
12057
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[], rootList: Object[] }}
|
|
12058
|
+
*/
|
|
11963
12059
|
export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
11964
12060
|
const pkgList = [];
|
|
11965
12061
|
const dependenciesList = [];
|
|
@@ -12078,6 +12174,14 @@ export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
|
12078
12174
|
};
|
|
12079
12175
|
}
|
|
12080
12176
|
|
|
12177
|
+
/**
|
|
12178
|
+
* Parse a Paket dependency manager lock file (paket.lock) and return the package list
|
|
12179
|
+
* and dependency tree.
|
|
12180
|
+
*
|
|
12181
|
+
* @param {string} paketLockData Raw text contents of the paket.lock file
|
|
12182
|
+
* @param {string} pkgLockFile Path to the paket.lock file, used for evidence properties
|
|
12183
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
12184
|
+
*/
|
|
12081
12185
|
export function parsePaketLockData(paketLockData, pkgLockFile) {
|
|
12082
12186
|
const pkgList = [];
|
|
12083
12187
|
const dependenciesList = [];
|
|
@@ -12252,6 +12356,16 @@ export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
|
12252
12356
|
const rootRequiresMap = {};
|
|
12253
12357
|
if (rootRequires) {
|
|
12254
12358
|
for (const rr of Object.keys(rootRequires)) {
|
|
12359
|
+
// Skip platform requirements (php, hhvm, ext-*, lib-*) — they are never
|
|
12360
|
+
// Composer package names and must not be used to identify root packages.
|
|
12361
|
+
if (
|
|
12362
|
+
rr === "php" ||
|
|
12363
|
+
rr === "hhvm" ||
|
|
12364
|
+
rr.startsWith("ext-") ||
|
|
12365
|
+
rr.startsWith("lib-")
|
|
12366
|
+
) {
|
|
12367
|
+
continue;
|
|
12368
|
+
}
|
|
12255
12369
|
rootRequiresMap[rr] = true;
|
|
12256
12370
|
}
|
|
12257
12371
|
}
|
|
@@ -12403,6 +12517,15 @@ export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
|
12403
12517
|
};
|
|
12404
12518
|
}
|
|
12405
12519
|
|
|
12520
|
+
/**
|
|
12521
|
+
* Parse an sbt dependency tree output file and return the package list and dependency tree.
|
|
12522
|
+
*
|
|
12523
|
+
* Reads a file produced by the sbt `dependencyTree` command and extracts Maven artifact
|
|
12524
|
+
* coordinates, building a hierarchical dependency graph. Evicted packages and ranges are ignored.
|
|
12525
|
+
*
|
|
12526
|
+
* @param {string} sbtTreeFile Path to the sbt dependency tree output file
|
|
12527
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
12528
|
+
*/
|
|
12406
12529
|
export function parseSbtTree(sbtTreeFile) {
|
|
12407
12530
|
const pkgList = [];
|
|
12408
12531
|
const dependenciesList = [];
|
|
@@ -12719,6 +12842,10 @@ export function convertOSQueryResults(
|
|
|
12719
12842
|
if (publisher === "null") {
|
|
12720
12843
|
publisher = "";
|
|
12721
12844
|
}
|
|
12845
|
+
// For vscode-extension purl type, the publisher is used as the namespace
|
|
12846
|
+
if (queryObj.purlType === "vscode-extension" && publisher) {
|
|
12847
|
+
group = publisher.toLowerCase();
|
|
12848
|
+
}
|
|
12722
12849
|
let scope;
|
|
12723
12850
|
const compScope = res.priority;
|
|
12724
12851
|
if (["required", "optional", "excluded"].includes(compScope)) {
|
|
@@ -12819,6 +12946,17 @@ export function convertOSQueryResults(
|
|
|
12819
12946
|
return pkgList;
|
|
12820
12947
|
}
|
|
12821
12948
|
|
|
12949
|
+
/**
|
|
12950
|
+
* Create a PackageURL object from a repository URL string, package type, and version.
|
|
12951
|
+
*
|
|
12952
|
+
* Supports HTTPS URLs, SSH `git@` URLs, Bitbucket SSH URLs, and local paths.
|
|
12953
|
+
* Extracts the namespace (host + path prefix) and repository name from the URL.
|
|
12954
|
+
*
|
|
12955
|
+
* @param {string} type PackageURL type (e.g. `"swift"`, `"generic"`)
|
|
12956
|
+
* @param {string} repoUrl Repository URL string
|
|
12957
|
+
* @param {string} version Package version
|
|
12958
|
+
* @returns {PackageURL|undefined} PackageURL object, or undefined for unsupported URL formats
|
|
12959
|
+
*/
|
|
12822
12960
|
export function purlFromUrlString(type, repoUrl, version) {
|
|
12823
12961
|
let namespace = "";
|
|
12824
12962
|
let name;
|
|
@@ -13111,6 +13249,20 @@ export async function collectMvnDependencies(
|
|
|
13111
13249
|
return jarNSMapping;
|
|
13112
13250
|
}
|
|
13113
13251
|
|
|
13252
|
+
/**
|
|
13253
|
+
* Collect Gradle project dependencies by scanning the Gradle cache directory for JAR files
|
|
13254
|
+
* and their associated POM files.
|
|
13255
|
+
*
|
|
13256
|
+
* Uses the `GRADLE_CACHE_DIR` or `GRADLE_USER_HOME` environment variables to locate the
|
|
13257
|
+
* Gradle files-2.1 cache, then delegates to {@link collectJarNS} to extract namespace
|
|
13258
|
+
* and purl information from those JARs.
|
|
13259
|
+
*
|
|
13260
|
+
* @param {string} _gradleCmd Gradle command (unused; reserved for future use)
|
|
13261
|
+
* @param {string} _basePath Base project path (unused; reserved for future use)
|
|
13262
|
+
* @param {boolean} _cleanup Whether to clean up temporary files (unused; reserved for future use)
|
|
13263
|
+
* @param {boolean} _includeCacheDir Whether to include cache directory (unused; reserved for future use)
|
|
13264
|
+
* @returns {Promise<Object>} JAR namespace mapping object returned by collectJarNS
|
|
13265
|
+
*/
|
|
13114
13266
|
export async function collectGradleDependencies(
|
|
13115
13267
|
_gradleCmd,
|
|
13116
13268
|
_basePath,
|
|
@@ -13324,6 +13476,16 @@ export async function collectJarNS(jarPath, pomPathMap = {}) {
|
|
|
13324
13476
|
return jarNSMapping;
|
|
13325
13477
|
}
|
|
13326
13478
|
|
|
13479
|
+
/**
|
|
13480
|
+
* Convert a JAR namespace mapping (produced by {@link collectJarNS}) into an array
|
|
13481
|
+
* of CycloneDX package component objects.
|
|
13482
|
+
*
|
|
13483
|
+
* Each entry in the mapping is resolved to a component with name, group, version,
|
|
13484
|
+
* purl, hashes, namespace properties, and source file evidence.
|
|
13485
|
+
*
|
|
13486
|
+
* @param {Object} jarNSMapping Map of purl string to `{ jarFile, pom, namespaces, hashes }`
|
|
13487
|
+
* @returns {Promise<Object[]>} Array of component objects derived from the JAR mapping
|
|
13488
|
+
*/
|
|
13327
13489
|
export async function convertJarNSToPackages(jarNSMapping) {
|
|
13328
13490
|
const pkgList = [];
|
|
13329
13491
|
for (const purl of Object.keys(jarNSMapping)) {
|
|
@@ -13427,6 +13589,12 @@ export function parsePomXml(pomXmlData) {
|
|
|
13427
13589
|
return undefined;
|
|
13428
13590
|
}
|
|
13429
13591
|
|
|
13592
|
+
/**
|
|
13593
|
+
* Parse a JAR MANIFEST.MF file and return its key-value pairs as an object.
|
|
13594
|
+
*
|
|
13595
|
+
* @param {string} jarMetadata Raw text contents of a MANIFEST.MF file
|
|
13596
|
+
* @returns {Object} Key-value pairs extracted from the manifest
|
|
13597
|
+
*/
|
|
13430
13598
|
export function parseJarManifest(jarMetadata) {
|
|
13431
13599
|
const metadata = {};
|
|
13432
13600
|
if (!jarMetadata) {
|
|
@@ -13444,6 +13612,12 @@ export function parseJarManifest(jarMetadata) {
|
|
|
13444
13612
|
return metadata;
|
|
13445
13613
|
}
|
|
13446
13614
|
|
|
13615
|
+
/**
|
|
13616
|
+
* Parse a Maven pom.properties file and return its key-value pairs as an object.
|
|
13617
|
+
*
|
|
13618
|
+
* @param {string} pomProperties Raw text contents of a pom.properties file
|
|
13619
|
+
* @returns {Object} Key-value pairs extracted from the properties file
|
|
13620
|
+
*/
|
|
13447
13621
|
export function parsePomProperties(pomProperties) {
|
|
13448
13622
|
const properties = {};
|
|
13449
13623
|
if (!pomProperties) {
|
|
@@ -13461,6 +13635,13 @@ export function parsePomProperties(pomProperties) {
|
|
|
13461
13635
|
return properties;
|
|
13462
13636
|
}
|
|
13463
13637
|
|
|
13638
|
+
/**
|
|
13639
|
+
* Encode a string for safe inclusion in a PackageURL, percent-encoding special characters
|
|
13640
|
+
* while preserving already-encoded `%40` sequences and keeping `:` and `/` unencoded.
|
|
13641
|
+
*
|
|
13642
|
+
* @param {string} s String to encode
|
|
13643
|
+
* @returns {string} Encoded string suitable for use in a PackageURL component
|
|
13644
|
+
*/
|
|
13464
13645
|
export function encodeForPurl(s) {
|
|
13465
13646
|
return s && !s.includes("%40")
|
|
13466
13647
|
? encodeURIComponent(s).replace(/%3A/g, ":").replace(/%2F/g, "/")
|
|
@@ -14364,10 +14545,10 @@ export async function parsePodfileLock(podfileLock, projectPath) {
|
|
|
14364
14545
|
},
|
|
14365
14546
|
];
|
|
14366
14547
|
let podspec = join(projectLocation, `${podName}.podspec`);
|
|
14367
|
-
if (!
|
|
14548
|
+
if (!safeExistsSync(podspec)) {
|
|
14368
14549
|
podspec = `${podspec}.json`;
|
|
14369
14550
|
}
|
|
14370
|
-
if (
|
|
14551
|
+
if (safeExistsSync(podspec)) {
|
|
14371
14552
|
dependency.metadata.properties.push({
|
|
14372
14553
|
name: "cdx:pods:podspecLocation",
|
|
14373
14554
|
value: podspec,
|
|
@@ -15112,6 +15293,19 @@ export function getAtomCommand() {
|
|
|
15112
15293
|
return "atom";
|
|
15113
15294
|
}
|
|
15114
15295
|
|
|
15296
|
+
/**
|
|
15297
|
+
* Execute the atom tool against a source directory or file with the given arguments.
|
|
15298
|
+
*
|
|
15299
|
+
* Resolves the atom binary via `getAtomCommand`, sets up the required environment
|
|
15300
|
+
* (including `JAVA_HOME` from `ATOM_JAVA_HOME` if set), and spawns the process.
|
|
15301
|
+
* Logs diagnostic messages for common failure modes such as unsupported Java versions,
|
|
15302
|
+
* missing `astgen`, and JVM crashes.
|
|
15303
|
+
*
|
|
15304
|
+
* @param {string} src Path to the source directory or file to analyse
|
|
15305
|
+
* @param {string[]} args Arguments to pass to the atom command
|
|
15306
|
+
* @param {Object} extra_env Additional environment variables to merge into the process environment
|
|
15307
|
+
* @returns {boolean} `true` if atom executed successfully and the language is supported; `false` otherwise
|
|
15308
|
+
*/
|
|
15115
15309
|
export function executeAtom(src, args, extra_env = {}) {
|
|
15116
15310
|
const cwd =
|
|
15117
15311
|
safeExistsSync(src) && lstatSync(src).isDirectory() ? src : dirname(src);
|
|
@@ -16195,6 +16389,13 @@ export function getPipTreeForPackages(
|
|
|
16195
16389
|
}
|
|
16196
16390
|
|
|
16197
16391
|
// taken from a very old package https://github.com/keithamus/parse-packagejson-name/blob/master/index.js
|
|
16392
|
+
/**
|
|
16393
|
+
* Parse a package.json `name` field (or a plain string) and extract its scope,
|
|
16394
|
+
* full name, project name, and module name components.
|
|
16395
|
+
*
|
|
16396
|
+
* @param {string|Object} name The package name string or an object with a `name` property
|
|
16397
|
+
* @returns {{ scope: string|null, fullName: string, projectName: string|null, moduleName: string|null }}
|
|
16398
|
+
*/
|
|
16198
16399
|
export function parsePackageJsonName(name) {
|
|
16199
16400
|
const nameRegExp = /^(?:@([^/]+)\/)?(([^.]+)(?:\.(.*))?)$/;
|
|
16200
16401
|
const returnObject = {
|
|
@@ -16373,6 +16574,16 @@ export async function addEvidenceForImports(
|
|
|
16373
16574
|
return pkgList;
|
|
16374
16575
|
}
|
|
16375
16576
|
|
|
16577
|
+
/**
|
|
16578
|
+
* Comparator function for sorting CycloneDX component objects.
|
|
16579
|
+
*
|
|
16580
|
+
* Compares components by `bom-ref`, then `purl`, then `name`, using locale-aware
|
|
16581
|
+
* string comparison on the first available key.
|
|
16582
|
+
*
|
|
16583
|
+
* @param {Object|string} a First component to compare
|
|
16584
|
+
* @param {Object|string} b Second component to compare
|
|
16585
|
+
* @returns {number} Negative, zero, or positive integer as required by Array.sort
|
|
16586
|
+
*/
|
|
16376
16587
|
export function componentSorter(a, b) {
|
|
16377
16588
|
if (a && b) {
|
|
16378
16589
|
for (const k of ["bom-ref", "purl", "name"]) {
|
|
@@ -16384,6 +16595,19 @@ export function componentSorter(a, b) {
|
|
|
16384
16595
|
return a.localeCompare(b);
|
|
16385
16596
|
}
|
|
16386
16597
|
|
|
16598
|
+
/**
|
|
16599
|
+
* Parse a CMake-generated dot/graphviz file and extract components and their dependency
|
|
16600
|
+
* relationships.
|
|
16601
|
+
*
|
|
16602
|
+
* The first `digraph` entry becomes the parent component. Subsequent `node` entries
|
|
16603
|
+
* with a `label` attribute are treated as direct dependencies, while commented
|
|
16604
|
+
* `node -> node` relationships are used to construct the dependency graph.
|
|
16605
|
+
*
|
|
16606
|
+
* @param {string} dotFile Path to the CMake-generated dot file
|
|
16607
|
+
* @param {string} pkgType PackageURL type to assign to extracted packages (e.g. `"generic"`)
|
|
16608
|
+
* @param {Object} options CLI options; may contain `projectGroup`, `projectName`, and `projectVersion`
|
|
16609
|
+
* @returns {{ parentComponent: Object, pkgList: Object[], dependenciesList: Object[] }}
|
|
16610
|
+
*/
|
|
16387
16611
|
export function parseCmakeDotFile(dotFile, pkgType, options = {}) {
|
|
16388
16612
|
const dotGraphData = readFileSync(dotFile, { encoding: "utf-8" });
|
|
16389
16613
|
const pkgList = [];
|
|
@@ -16493,6 +16717,19 @@ export function parseCmakeDotFile(dotFile, pkgType, options = {}) {
|
|
|
16493
16717
|
};
|
|
16494
16718
|
}
|
|
16495
16719
|
|
|
16720
|
+
/**
|
|
16721
|
+
* Parse a CMake-like build file (CMakeLists.txt, meson.build, etc.) and extract the
|
|
16722
|
+
* parent component and list of dependency packages.
|
|
16723
|
+
*
|
|
16724
|
+
* Handles `set`, `project`, `find_package`, `find_library`, `find_dependency`,
|
|
16725
|
+
* `find_file`, `FetchContent_MakeAvailable`, and `dependency()` directives.
|
|
16726
|
+
* Uses the MesonWrapDB to improve name resolution confidence.
|
|
16727
|
+
*
|
|
16728
|
+
* @param {string} cmakeListFile Path to the CMake-like build file
|
|
16729
|
+
* @param {string} pkgType PackageURL type to assign to extracted packages (e.g. `"generic"`)
|
|
16730
|
+
* @param {Object} options CLI options; may contain `projectGroup`, `projectName`, and `projectVersion`
|
|
16731
|
+
* @returns {{ parentComponent: Object, pkgList: Object[] }}
|
|
16732
|
+
*/
|
|
16496
16733
|
export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
16497
16734
|
let cmakeListData = readFileSync(cmakeListFile, { encoding: "utf-8" });
|
|
16498
16735
|
const pkgList = [];
|
|
@@ -16746,6 +16983,14 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
|
16746
16983
|
};
|
|
16747
16984
|
}
|
|
16748
16985
|
|
|
16986
|
+
/**
|
|
16987
|
+
* Find the OS package component that provides a given file, by searching the
|
|
16988
|
+
* `PkgProvides` property of each package in the OS package list.
|
|
16989
|
+
*
|
|
16990
|
+
* @param {string} afile Filename or path to look up (matched case-insensitively)
|
|
16991
|
+
* @param {Object[]} osPkgsList Array of OS package component objects to search
|
|
16992
|
+
* @returns {Object|undefined} The matching OS package component, or undefined if not found
|
|
16993
|
+
*/
|
|
16749
16994
|
export function getOSPackageForFile(afile, osPkgsList) {
|
|
16750
16995
|
for (const ospkg of osPkgsList) {
|
|
16751
16996
|
for (const props of ospkg.properties || []) {
|
|
@@ -17359,6 +17604,18 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
|
|
|
17359
17604
|
};
|
|
17360
17605
|
}
|
|
17361
17606
|
|
|
17607
|
+
/**
|
|
17608
|
+
* Enrich .NET package components with occurrence evidence and imported module/method
|
|
17609
|
+
* information from a dosai dependency slices file.
|
|
17610
|
+
*
|
|
17611
|
+
* Builds a mapping of DLL filenames to purls using the `PackageFiles` property of each
|
|
17612
|
+
* package, then reads the slices file to add occurrence locations, imported modules,
|
|
17613
|
+
* called methods, and assembly version information where available.
|
|
17614
|
+
*
|
|
17615
|
+
* @param {Object[]} pkgList Array of .NET package component objects to enrich
|
|
17616
|
+
* @param {string} slicesFile Path to the dosai dependency slices JSON file
|
|
17617
|
+
* @returns {Object[]} The enriched package list (same array, mutated in place)
|
|
17618
|
+
*/
|
|
17362
17619
|
export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
17363
17620
|
// We need two datastructures.
|
|
17364
17621
|
// dll to purl mapping from the pkgList
|
|
@@ -17793,7 +18050,7 @@ export function collectSharedLibs(
|
|
|
17793
18050
|
}
|
|
17794
18051
|
|
|
17795
18052
|
function collectAllLdConfs(basePath, ldConf, allLdConfDirs, libPaths) {
|
|
17796
|
-
if (ldConf &&
|
|
18053
|
+
if (ldConf && safeExistsSync(join(basePath, ldConf))) {
|
|
17797
18054
|
const ldConfData = readFileSync(join(basePath, ldConf), "utf-8");
|
|
17798
18055
|
for (let line of ldConfData.split("\n")) {
|
|
17799
18056
|
line = line.replace("\r", "").trim();
|
|
@@ -17979,6 +18236,14 @@ export function retrieveCdxgenVersion() {
|
|
|
17979
18236
|
return `\x1b[1mCycloneDX Generator ${packageJson.version}\x1b[0m\nRuntime: ${runtimeInfo.runtime}, Version: ${runtimeInfo.version}`;
|
|
17980
18237
|
}
|
|
17981
18238
|
|
|
18239
|
+
/**
|
|
18240
|
+
* Retrieve the version of the cdxgen plugins binary package from package.json.
|
|
18241
|
+
*
|
|
18242
|
+
* Reads the local package.json and searches the `optionalDependencies` for a package
|
|
18243
|
+
* whose name starts with `@cdxgen/cdxgen-plugins-bin`, returning its declared version.
|
|
18244
|
+
*
|
|
18245
|
+
* @returns {string|undefined} Version string of the plugins binary package, or undefined if not found
|
|
18246
|
+
*/
|
|
17982
18247
|
export function retrieveCdxgenPluginVersion() {
|
|
17983
18248
|
const packageJsonAsString = readFileSync(
|
|
17984
18249
|
join(dirNameStr, "package.json"),
|
|
@@ -18043,3 +18308,13 @@ export function splitCommandArgs(commandString) {
|
|
|
18043
18308
|
}
|
|
18044
18309
|
return args;
|
|
18045
18310
|
}
|
|
18311
|
+
|
|
18312
|
+
/**
|
|
18313
|
+
* Convert hyphenated strings to camel case.
|
|
18314
|
+
*
|
|
18315
|
+
* @param {String} str String to convert
|
|
18316
|
+
* @returns {String} camelCased string
|
|
18317
|
+
*/
|
|
18318
|
+
export function toCamel(str) {
|
|
18319
|
+
return str.replace(/-([a-z])/g, (_, g) => g.toUpperCase());
|
|
18320
|
+
}
|