@cyclonedx/cdxgen 12.1.1 → 12.1.3
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 +27 -9
- package/bin/cdxgen.js +1 -1
- package/data/spdx.schema.json +35 -2
- package/data/templates/asvs-5.0.cdx.json +1727 -3471
- package/lib/cli/index.js +32 -4
- package/lib/evinser/evinser.js +2 -8
- package/lib/helpers/display.js +1 -1
- package/lib/helpers/envcontext.js +10 -2
- package/lib/helpers/utils.js +487 -115
- package/lib/helpers/utils.poku.js +200 -3
- package/lib/helpers/validator.js +37 -3
- package/lib/managers/binary.js +34 -12
- package/lib/managers/containerutils.js +68 -0
- package/lib/managers/docker.getConnection.poku.js +61 -0
- package/lib/managers/docker.js +72 -119
- package/lib/parsers/iri.js +1 -2
- package/lib/server/server.js +164 -34
- package/lib/server/server.poku.js +232 -10
- package/lib/stages/postgen/annotator.js +281 -3
- package/lib/stages/postgen/postgen.js +4 -7
- package/lib/third-party/arborist/lib/diff.js +1 -1
- package/lib/third-party/arborist/lib/node.js +1 -1
- package/lib/third-party/arborist/lib/yarn-lock.js +1 -1
- package/package.json +22 -326
- package/types/bin/dependencies.d.ts.map +1 -1
- package/types/bin/licenses.d.ts +3 -0
- package/types/bin/licenses.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +1 -1
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/validator.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/containerutils.d.ts +3 -0
- package/types/lib/managers/containerutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +0 -2
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/iri.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +14 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/bin/dependencies.js +0 -131
- package/lib/helpers/dependencies.poku.js +0 -11
package/lib/helpers/utils.js
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
satisfies,
|
|
49
49
|
valid,
|
|
50
50
|
} from "semver";
|
|
51
|
+
import { v4 as uuidv4 } from "uuid";
|
|
51
52
|
import { xml2js } from "xml-js";
|
|
52
53
|
import { parse as _load } from "yaml";
|
|
53
54
|
|
|
@@ -160,6 +161,20 @@ export function safeSpawnSync(command, args, options) {
|
|
|
160
161
|
if (!options.timeout) {
|
|
161
162
|
options.timeout = TIMEOUT_MS;
|
|
162
163
|
}
|
|
164
|
+
// Check for -S for python invocations in secure mode
|
|
165
|
+
if (command.includes("python") && (!args?.length || args[0] !== "-S")) {
|
|
166
|
+
if (isSecureMode) {
|
|
167
|
+
console.warn(
|
|
168
|
+
"\x1b[1;35mNotice: Running python command without '-S' argument. This is a bug in cdxgen. Please report with an example repo here https://github.com/cdxgen/cdxgen/issues.\x1b[0m",
|
|
169
|
+
);
|
|
170
|
+
} else if (process.env?.CDXGEN_IN_CONTAINER === "true") {
|
|
171
|
+
console.log("Running python command without '-S' argument.");
|
|
172
|
+
} else {
|
|
173
|
+
console.warn(
|
|
174
|
+
"\x1b[1;35mNotice: Running python command without '-S' argument. Only run cdxgen in trusted directories to prevent auto-executing local scripts.\x1b[0m",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
163
178
|
traceLog("spawn", { command, args, ...options });
|
|
164
179
|
commandsExecuted.add(command);
|
|
165
180
|
// Fix for DEP0190 warning
|
|
@@ -1033,18 +1048,10 @@ export function getKnownLicense(licenseUrl, pkg) {
|
|
|
1033
1048
|
return { id: akLic.license, name: akLic.licenseName };
|
|
1034
1049
|
}
|
|
1035
1050
|
}
|
|
1036
|
-
if (
|
|
1037
|
-
akLic.urlIncludes &&
|
|
1038
|
-
licenseUrl &&
|
|
1039
|
-
licenseUrl.includes(akLic.urlIncludes)
|
|
1040
|
-
) {
|
|
1051
|
+
if (akLic.urlIncludes && licenseUrl?.includes(akLic.urlIncludes)) {
|
|
1041
1052
|
return { id: akLic.license, name: akLic.licenseName };
|
|
1042
1053
|
}
|
|
1043
|
-
if (
|
|
1044
|
-
akLic.urlEndswith &&
|
|
1045
|
-
licenseUrl &&
|
|
1046
|
-
licenseUrl.endsWith(akLic.urlEndswith)
|
|
1047
|
-
) {
|
|
1054
|
+
if (akLic.urlEndswith && licenseUrl?.endsWith(akLic.urlEndswith)) {
|
|
1048
1055
|
return { id: akLic.license, name: akLic.licenseName };
|
|
1049
1056
|
}
|
|
1050
1057
|
}
|
|
@@ -1265,7 +1272,7 @@ export async function parsePkgJson(pkgJsonFile, simple = false) {
|
|
|
1265
1272
|
// continue regardless of error
|
|
1266
1273
|
}
|
|
1267
1274
|
}
|
|
1268
|
-
if (!simple && shouldFetchLicense() && pkgList
|
|
1275
|
+
if (!simple && shouldFetchLicense() && pkgList?.length) {
|
|
1269
1276
|
if (DEBUG_MODE) {
|
|
1270
1277
|
console.log(
|
|
1271
1278
|
`About to fetch license information for ${pkgList.length} packages in parsePkgJson`,
|
|
@@ -1786,7 +1793,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1786
1793
|
options,
|
|
1787
1794
|
));
|
|
1788
1795
|
|
|
1789
|
-
if (shouldFetchLicense() && pkgList
|
|
1796
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
1790
1797
|
if (DEBUG_MODE) {
|
|
1791
1798
|
console.log(
|
|
1792
1799
|
`About to fetch license information for ${pkgList.length} packages in parsePkgLock`,
|
|
@@ -2419,7 +2426,7 @@ export async function parseYarnLock(
|
|
|
2419
2426
|
}
|
|
2420
2427
|
}
|
|
2421
2428
|
|
|
2422
|
-
if (shouldFetchLicense() && pkgList
|
|
2429
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
2423
2430
|
if (DEBUG_MODE) {
|
|
2424
2431
|
console.log(
|
|
2425
2432
|
`About to fetch license information for ${pkgList.length} packages in parseYarnLock`,
|
|
@@ -2496,7 +2503,7 @@ export async function parseNodeShrinkwrap(swFile) {
|
|
|
2496
2503
|
}
|
|
2497
2504
|
}
|
|
2498
2505
|
}
|
|
2499
|
-
if (shouldFetchLicense() && pkgList
|
|
2506
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
2500
2507
|
if (DEBUG_MODE) {
|
|
2501
2508
|
console.log(
|
|
2502
2509
|
`About to fetch license information for ${pkgList.length} packages in parseNodeShrinkwrap`,
|
|
@@ -3698,7 +3705,7 @@ export async function parsePnpmLock(
|
|
|
3698
3705
|
pkgList = await pnpmMetadata(pkgList, pnpmLock);
|
|
3699
3706
|
}
|
|
3700
3707
|
|
|
3701
|
-
if (shouldFetchLicense() && pkgList
|
|
3708
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
3702
3709
|
if (DEBUG_MODE) {
|
|
3703
3710
|
console.log(
|
|
3704
3711
|
`About to fetch license information for ${pkgList.length} packages in parsePnpmLock`,
|
|
@@ -3759,7 +3766,7 @@ export async function parseBowerJson(bowerJsonFile) {
|
|
|
3759
3766
|
// continue regardless of error
|
|
3760
3767
|
}
|
|
3761
3768
|
}
|
|
3762
|
-
if (shouldFetchLicense() && pkgList
|
|
3769
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
3763
3770
|
if (DEBUG_MODE) {
|
|
3764
3771
|
console.log(
|
|
3765
3772
|
`About to fetch license information for ${pkgList.length} packages in parseBowerJson`,
|
|
@@ -3857,7 +3864,7 @@ export async function parseMinJs(minJsFile) {
|
|
|
3857
3864
|
// continue regardless of error
|
|
3858
3865
|
}
|
|
3859
3866
|
}
|
|
3860
|
-
if (shouldFetchLicense() && pkgList
|
|
3867
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
3861
3868
|
if (DEBUG_MODE) {
|
|
3862
3869
|
console.log(
|
|
3863
3870
|
`About to fetch license information for ${pkgList.length} packages in parseMinJs`,
|
|
@@ -6132,6 +6139,9 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
6132
6139
|
).toString();
|
|
6133
6140
|
pkg.purl = purlString;
|
|
6134
6141
|
pkg["bom-ref"] = decodeURIComponent(purlString);
|
|
6142
|
+
if (parentComponent && pkg["bom-ref"] === parentComponent["bom-ref"]) {
|
|
6143
|
+
continue;
|
|
6144
|
+
}
|
|
6135
6145
|
pkg.evidence = {
|
|
6136
6146
|
identity: {
|
|
6137
6147
|
field: "purl",
|
|
@@ -6387,6 +6397,9 @@ export async function parseReqFile(reqFile, fetchDepsInfo = false) {
|
|
|
6387
6397
|
return await parseReqData(reqFile, null, fetchDepsInfo);
|
|
6388
6398
|
}
|
|
6389
6399
|
|
|
6400
|
+
const LICENSE_ID_COMMENTS_PATTERN =
|
|
6401
|
+
/^(Apache-2\.0|MIT|ISC|GPL-|LGPL-|BSD-[23]-Clause)/i;
|
|
6402
|
+
|
|
6390
6403
|
/**
|
|
6391
6404
|
* Method to parse requirements.txt file. Must only be used internally.
|
|
6392
6405
|
*
|
|
@@ -6434,6 +6447,12 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6434
6447
|
if (!l || l.startsWith("#") || l.startsWith("-")) {
|
|
6435
6448
|
continue;
|
|
6436
6449
|
}
|
|
6450
|
+
let comment = null;
|
|
6451
|
+
const commentMatch = l.match(/\s+#(.*)$/);
|
|
6452
|
+
if (commentMatch) {
|
|
6453
|
+
comment = commentMatch[1].trim();
|
|
6454
|
+
l = l.substring(0, commentMatch.index).trim();
|
|
6455
|
+
}
|
|
6437
6456
|
const properties = reqFile
|
|
6438
6457
|
? [
|
|
6439
6458
|
{
|
|
@@ -6482,6 +6501,21 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6482
6501
|
scope: compScope,
|
|
6483
6502
|
evidence,
|
|
6484
6503
|
};
|
|
6504
|
+
if (comment) {
|
|
6505
|
+
apkg.licenses = comment
|
|
6506
|
+
.split("/")
|
|
6507
|
+
.map((c) => {
|
|
6508
|
+
const licenseObj = {};
|
|
6509
|
+
const cId = c.trim();
|
|
6510
|
+
if (cId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
6511
|
+
licenseObj.id = cId;
|
|
6512
|
+
} else {
|
|
6513
|
+
return undefined;
|
|
6514
|
+
}
|
|
6515
|
+
return { license: licenseObj };
|
|
6516
|
+
})
|
|
6517
|
+
.filter((l) => l !== undefined);
|
|
6518
|
+
}
|
|
6485
6519
|
if (extras && extras.length > 0) {
|
|
6486
6520
|
properties.push({
|
|
6487
6521
|
name: "cdx:pypi:extras",
|
|
@@ -6533,6 +6567,21 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6533
6567
|
scope: compScope,
|
|
6534
6568
|
evidence,
|
|
6535
6569
|
};
|
|
6570
|
+
if (comment) {
|
|
6571
|
+
apkg.licenses = comment
|
|
6572
|
+
.split("/")
|
|
6573
|
+
.map((c) => {
|
|
6574
|
+
const licenseObj = {};
|
|
6575
|
+
const cId = c.trim();
|
|
6576
|
+
if (cId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
6577
|
+
licenseObj.id = cId;
|
|
6578
|
+
} else {
|
|
6579
|
+
return undefined;
|
|
6580
|
+
}
|
|
6581
|
+
return { license: licenseObj };
|
|
6582
|
+
})
|
|
6583
|
+
.filter((l) => l !== undefined);
|
|
6584
|
+
}
|
|
6536
6585
|
if (versionSpecifiers && !versionSpecifiers.startsWith("==")) {
|
|
6537
6586
|
properties.push({
|
|
6538
6587
|
name: "cdx:pypi:versionSpecifiers",
|
|
@@ -7257,18 +7306,20 @@ export async function parseGoModData(goModData, gosumMap) {
|
|
|
7257
7306
|
isTool = false;
|
|
7258
7307
|
continue;
|
|
7259
7308
|
}
|
|
7260
|
-
if (l.includes("tool ")) {
|
|
7309
|
+
if (l.includes("tool ") || isTool) {
|
|
7261
7310
|
continue;
|
|
7262
7311
|
}
|
|
7263
|
-
if (
|
|
7312
|
+
if (l.startsWith("toolchain ")) {
|
|
7313
|
+
const toolchainVer = l.split(" ").pop().trim();
|
|
7314
|
+
parentComponent.properties = [
|
|
7315
|
+
{ name: "cdx:go:toolchain", value: toolchainVer },
|
|
7316
|
+
];
|
|
7264
7317
|
continue;
|
|
7265
7318
|
}
|
|
7266
|
-
|
|
7267
7319
|
// Skip go.mod file headers, whitespace, and/or comments
|
|
7268
7320
|
if (
|
|
7269
7321
|
l.startsWith("go ") ||
|
|
7270
7322
|
//TODO: should toolchain be considered as a dependency
|
|
7271
|
-
l.startsWith("toolchain ") ||
|
|
7272
7323
|
l.includes(")") ||
|
|
7273
7324
|
l.trim() === "" ||
|
|
7274
7325
|
l.trim().startsWith("//")
|
|
@@ -7378,14 +7429,17 @@ export async function parseGoListDep(rawOutput, gosumMap) {
|
|
|
7378
7429
|
.split("\n")
|
|
7379
7430
|
.filter((p) => p.trim().replace(/["']/g, "").length);
|
|
7380
7431
|
for (const l of pkgs) {
|
|
7381
|
-
const verArr = l.trim().replace(/["']/g, "").split("
|
|
7432
|
+
const verArr = l.trim().replace(/["']/g, "").split("|");
|
|
7382
7433
|
if (verArr && verArr.length >= 5) {
|
|
7383
7434
|
const key = `${verArr[0]}-${verArr[1]}`;
|
|
7384
7435
|
// Filter duplicates
|
|
7385
7436
|
if (!keys_cache[key]) {
|
|
7386
7437
|
keys_cache[key] = key;
|
|
7387
7438
|
const version = verArr[1];
|
|
7388
|
-
|
|
7439
|
+
let gosumHash = gosumMap[`${verArr[0]}@${version}`];
|
|
7440
|
+
if (!gosumHash && verArr.length >= 8 && verArr[8]?.length) {
|
|
7441
|
+
gosumHash = `sha256-${verArr[8].replace("h1:", "")}`;
|
|
7442
|
+
}
|
|
7389
7443
|
const component = await getGoPkgComponent(
|
|
7390
7444
|
"",
|
|
7391
7445
|
verArr[0],
|
|
@@ -7412,6 +7466,43 @@ export async function parseGoListDep(rawOutput, gosumMap) {
|
|
|
7412
7466
|
value: verArr[2],
|
|
7413
7467
|
},
|
|
7414
7468
|
];
|
|
7469
|
+
if (
|
|
7470
|
+
verArr.length >= 6 &&
|
|
7471
|
+
verArr[6]?.length &&
|
|
7472
|
+
verArr[6] !== "<nil>"
|
|
7473
|
+
) {
|
|
7474
|
+
component.properties.push({
|
|
7475
|
+
name: "cdx:go:creation_time",
|
|
7476
|
+
value: verArr[6],
|
|
7477
|
+
});
|
|
7478
|
+
}
|
|
7479
|
+
if (verArr.length >= 7 && verArr[7]?.length) {
|
|
7480
|
+
component.properties.push({
|
|
7481
|
+
name: "cdx:go:deprecated",
|
|
7482
|
+
value: verArr[7],
|
|
7483
|
+
});
|
|
7484
|
+
}
|
|
7485
|
+
if (verArr.length >= 9 && verArr[9]?.length) {
|
|
7486
|
+
component.properties.push({
|
|
7487
|
+
name: "cdx:go:local_dir",
|
|
7488
|
+
value: verArr[9],
|
|
7489
|
+
});
|
|
7490
|
+
if (safeExistsSync(join(verArr[9], "LICENSE"))) {
|
|
7491
|
+
const licenseText = readFileSync(join(verArr[9], "LICENSE"), {
|
|
7492
|
+
encoding: "utf-8",
|
|
7493
|
+
});
|
|
7494
|
+
if (licenseText?.length) {
|
|
7495
|
+
component.licenses = [
|
|
7496
|
+
{
|
|
7497
|
+
license: {
|
|
7498
|
+
name: "CUSTOM",
|
|
7499
|
+
text: { contentType: "text/plain", content: licenseText },
|
|
7500
|
+
},
|
|
7501
|
+
},
|
|
7502
|
+
];
|
|
7503
|
+
}
|
|
7504
|
+
}
|
|
7505
|
+
}
|
|
7415
7506
|
if (verArr.length > 5 && verArr[5] === "true") {
|
|
7416
7507
|
parentComponent = component;
|
|
7417
7508
|
} else {
|
|
@@ -9835,82 +9926,250 @@ export function parseGitHubWorkflowData(f) {
|
|
|
9835
9926
|
return pkgList;
|
|
9836
9927
|
}
|
|
9837
9928
|
const lines = ghwData.split("\n");
|
|
9929
|
+
// workflow-related values
|
|
9930
|
+
const workflowName = yamlObj.name || path.basename(f, path.extname(f));
|
|
9931
|
+
const workflowTriggers = yamlObj.on || yamlObj.true;
|
|
9932
|
+
const workflowPermissions = yamlObj.permissions || {};
|
|
9933
|
+
let hasWritePermissions = analyzePermissions(workflowPermissions);
|
|
9934
|
+
// GitHub of course supports strings such as "write-all" to make it easy to create supply-chain attacks.
|
|
9935
|
+
if (
|
|
9936
|
+
(typeof workflowPermissions === "string" ||
|
|
9937
|
+
workflowPermissions instanceof String) &&
|
|
9938
|
+
workflowPermissions.includes("write")
|
|
9939
|
+
) {
|
|
9940
|
+
hasWritePermissions = true;
|
|
9941
|
+
}
|
|
9942
|
+
const workflowConcurrency = yamlObj.concurrency || {};
|
|
9943
|
+
const _workflowEnv = yamlObj.env || {};
|
|
9944
|
+
const hasIdTokenWrite = workflowPermissions?.["id-token"] === "write";
|
|
9838
9945
|
for (const jobName of Object.keys(yamlObj.jobs)) {
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9946
|
+
const job = yamlObj.jobs[jobName];
|
|
9947
|
+
if (!job.steps) {
|
|
9948
|
+
continue;
|
|
9949
|
+
}
|
|
9950
|
+
// job-related values
|
|
9951
|
+
const jobRunner = job["runs-on"] || "unknown";
|
|
9952
|
+
const jobEnvironment = job.environment?.name || job.environment || "";
|
|
9953
|
+
const jobTimeout = job["timeout-minutes"] || null;
|
|
9954
|
+
const jobPermissions = job.permissions || {};
|
|
9955
|
+
const jobServices = job.services ? Object.keys(job.services) : [];
|
|
9956
|
+
let jobNeeds = job.needs || [];
|
|
9957
|
+
if (!Array.isArray(jobNeeds)) {
|
|
9958
|
+
jobNeeds = [jobNeeds];
|
|
9959
|
+
}
|
|
9960
|
+
const _jobIf = job.if || "";
|
|
9961
|
+
const _jobStrategy = job.strategy ? JSON.stringify(job.strategy) : "";
|
|
9962
|
+
const jobHasWritePermissions = analyzePermissions(jobPermissions);
|
|
9963
|
+
for (const step of job.steps) {
|
|
9964
|
+
if (step.uses) {
|
|
9965
|
+
const tmpA = step.uses.split("@");
|
|
9966
|
+
if (tmpA.length !== 2) {
|
|
9967
|
+
continue;
|
|
9968
|
+
}
|
|
9969
|
+
const groupName = tmpA[0];
|
|
9970
|
+
let name = groupName;
|
|
9971
|
+
let group = "";
|
|
9972
|
+
const tagOrCommit = tmpA[1];
|
|
9973
|
+
let version = tagOrCommit;
|
|
9974
|
+
const tmpB = groupName.split("/");
|
|
9975
|
+
if (tmpB.length >= 2) {
|
|
9976
|
+
name = tmpB.pop();
|
|
9977
|
+
group = tmpB.join("/");
|
|
9978
|
+
} else if (tmpB.length === 1) {
|
|
9979
|
+
name = tmpB[0];
|
|
9980
|
+
group = "";
|
|
9981
|
+
}
|
|
9982
|
+
const versionPinningType = getVersionPinningType(tagOrCommit);
|
|
9983
|
+
const isShaPinned = versionPinningType === "sha";
|
|
9984
|
+
const _isTagPinned = versionPinningType === "tag";
|
|
9985
|
+
const _isBranchRef = versionPinningType === "branch";
|
|
9986
|
+
let lineNum = -1;
|
|
9987
|
+
const stepLineMatch = ghwData.indexOf(step.uses);
|
|
9988
|
+
if (stepLineMatch >= 0) {
|
|
9989
|
+
lineNum = ghwData.substring(0, stepLineMatch).split("\n").length - 1;
|
|
9990
|
+
}
|
|
9991
|
+
if (lineNum >= 0 && lines[lineNum]) {
|
|
9992
|
+
const line = lines[lineNum];
|
|
9993
|
+
const commentMatch = line.match(/#\s*v?([0-9]+(?:\.[0-9]+)*)/);
|
|
9994
|
+
if (commentMatch?.[1]) {
|
|
9995
|
+
version = commentMatch[1];
|
|
9845
9996
|
}
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
if (
|
|
9853
|
-
|
|
9854
|
-
group = tmpB.join("/");
|
|
9855
|
-
} else if (tmpB.length === 1) {
|
|
9856
|
-
name = tmpB[0];
|
|
9857
|
-
group = "";
|
|
9997
|
+
}
|
|
9998
|
+
const key = `${group}-${name}-${version}`;
|
|
9999
|
+
let confidence = 0.6;
|
|
10000
|
+
if (!keys_cache[key] && name && version) {
|
|
10001
|
+
keys_cache[key] = key;
|
|
10002
|
+
let fullName = name;
|
|
10003
|
+
if (group.length) {
|
|
10004
|
+
fullName = `${group}/${name}`;
|
|
9858
10005
|
}
|
|
9859
|
-
let
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
10006
|
+
let purl = `pkg:github/${fullName}@${version}`;
|
|
10007
|
+
if (tagOrCommit && version !== tagOrCommit) {
|
|
10008
|
+
const qualifierDesc = tagOrCommit.startsWith("v")
|
|
10009
|
+
? "tag"
|
|
10010
|
+
: "commit";
|
|
10011
|
+
purl = `${purl}?${qualifierDesc}=${tagOrCommit}`;
|
|
10012
|
+
confidence = 0.7;
|
|
9864
10013
|
}
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
10014
|
+
const properties = [
|
|
10015
|
+
{ name: "SrcFile", value: f },
|
|
10016
|
+
{ name: "cdx:github:workflow:name", value: workflowName },
|
|
10017
|
+
{ name: "cdx:github:job:name", value: jobName },
|
|
10018
|
+
{
|
|
10019
|
+
name: "cdx:github:job:runner",
|
|
10020
|
+
value: Array.isArray(jobRunner) ? jobRunner.join(",") : jobRunner,
|
|
10021
|
+
},
|
|
10022
|
+
{ name: "cdx:github:action:uses", value: step.uses },
|
|
10023
|
+
{
|
|
10024
|
+
name: "cdx:github:action:versionPinningType",
|
|
10025
|
+
value: versionPinningType,
|
|
10026
|
+
},
|
|
10027
|
+
{
|
|
10028
|
+
name: "cdx:github:action:isShaPinned",
|
|
10029
|
+
value: isShaPinned.toString(),
|
|
10030
|
+
},
|
|
10031
|
+
];
|
|
10032
|
+
if (step.name) {
|
|
10033
|
+
properties.push({ name: "cdx:github:step:name", value: step.name });
|
|
9871
10034
|
}
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
10035
|
+
if (step.if) {
|
|
10036
|
+
properties.push({
|
|
10037
|
+
name: "cdx:github:step:condition",
|
|
10038
|
+
value: step.if,
|
|
10039
|
+
});
|
|
10040
|
+
}
|
|
10041
|
+
if (step["continue-on-error"]) {
|
|
10042
|
+
properties.push({
|
|
10043
|
+
name: "cdx:github:step:continueOnError",
|
|
10044
|
+
value: "true",
|
|
10045
|
+
});
|
|
10046
|
+
}
|
|
10047
|
+
if (step.timeout) {
|
|
10048
|
+
properties.push({
|
|
10049
|
+
name: "cdx:github:step:timeout",
|
|
10050
|
+
value: step.timeout.toString(),
|
|
10051
|
+
});
|
|
10052
|
+
}
|
|
10053
|
+
if (jobEnvironment) {
|
|
10054
|
+
properties.push({
|
|
10055
|
+
name: "cdx:github:job:environment",
|
|
10056
|
+
value: jobEnvironment,
|
|
10057
|
+
});
|
|
10058
|
+
}
|
|
10059
|
+
if (jobTimeout) {
|
|
10060
|
+
properties.push({
|
|
10061
|
+
name: "cdx:github:job:timeoutMinutes",
|
|
10062
|
+
value: jobTimeout.toString(),
|
|
10063
|
+
});
|
|
10064
|
+
}
|
|
10065
|
+
if (jobHasWritePermissions) {
|
|
10066
|
+
properties.push({
|
|
10067
|
+
name: "cdx:github:job:hasWritePermissions",
|
|
10068
|
+
value: "true",
|
|
10069
|
+
});
|
|
10070
|
+
}
|
|
10071
|
+
if (jobServices.length > 0) {
|
|
10072
|
+
properties.push({
|
|
10073
|
+
name: "cdx:github:job:services",
|
|
10074
|
+
value: jobServices.join(","),
|
|
10075
|
+
});
|
|
10076
|
+
}
|
|
10077
|
+
if (jobNeeds.length > 0) {
|
|
10078
|
+
properties.push({
|
|
10079
|
+
name: "cdx:github:job:needs",
|
|
10080
|
+
value: jobNeeds.join(","),
|
|
10081
|
+
});
|
|
10082
|
+
}
|
|
10083
|
+
if (hasWritePermissions) {
|
|
10084
|
+
properties.push({
|
|
10085
|
+
name: "cdx:github:workflow:hasWritePermissions",
|
|
10086
|
+
value: "true",
|
|
10087
|
+
});
|
|
10088
|
+
}
|
|
10089
|
+
if (hasIdTokenWrite) {
|
|
10090
|
+
properties.push({
|
|
10091
|
+
name: "cdx:github:workflow:hasIdTokenWrite",
|
|
10092
|
+
value: "true",
|
|
10093
|
+
});
|
|
10094
|
+
}
|
|
10095
|
+
if (workflowConcurrency?.group) {
|
|
10096
|
+
properties.push({
|
|
10097
|
+
name: "cdx:github:workflow:concurrencyGroup",
|
|
10098
|
+
value: workflowConcurrency.group,
|
|
10099
|
+
});
|
|
10100
|
+
}
|
|
10101
|
+
if (group?.startsWith("github/") || group === "actions") {
|
|
10102
|
+
properties.push({ name: "cdx:actions:isOfficial", value: "true" });
|
|
10103
|
+
}
|
|
10104
|
+
if (group?.startsWith("github/")) {
|
|
10105
|
+
properties.push({ name: "cdx:actions:isVerified", value: "true" });
|
|
10106
|
+
}
|
|
10107
|
+
if (workflowTriggers) {
|
|
10108
|
+
const triggers =
|
|
10109
|
+
typeof workflowTriggers === "string"
|
|
10110
|
+
? workflowTriggers
|
|
10111
|
+
: Object.keys(workflowTriggers).join(",");
|
|
10112
|
+
properties.push({
|
|
10113
|
+
name: "cdx:github:workflow:triggers",
|
|
10114
|
+
value: triggers,
|
|
10115
|
+
});
|
|
10116
|
+
}
|
|
10117
|
+
pkgList.push({
|
|
10118
|
+
group,
|
|
10119
|
+
name,
|
|
10120
|
+
version,
|
|
10121
|
+
purl,
|
|
10122
|
+
properties,
|
|
10123
|
+
evidence: {
|
|
10124
|
+
identity: {
|
|
10125
|
+
field: "purl",
|
|
10126
|
+
confidence,
|
|
10127
|
+
methods: [
|
|
10128
|
+
{
|
|
10129
|
+
technique: "source-code-analysis",
|
|
10130
|
+
confidence,
|
|
10131
|
+
value: f,
|
|
10132
|
+
},
|
|
10133
|
+
],
|
|
9892
10134
|
},
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
10135
|
+
},
|
|
10136
|
+
});
|
|
10137
|
+
}
|
|
10138
|
+
}
|
|
10139
|
+
if (step.run) {
|
|
10140
|
+
const runLineNum = ghwData.indexOf(step.run);
|
|
10141
|
+
const runLine =
|
|
10142
|
+
runLineNum >= 0
|
|
10143
|
+
? ghwData.substring(0, runLineNum).split("\n").length
|
|
10144
|
+
: -1;
|
|
10145
|
+
const pkgCommands = extractPackageManagerCommands(step.run);
|
|
10146
|
+
for (const pkgCmd of pkgCommands) {
|
|
10147
|
+
const key = `run-${pkgCmd.name}-${pkgCmd.version || "latest"}`;
|
|
10148
|
+
if (!keys_cache[key]) {
|
|
10149
|
+
keys_cache[key] = key;
|
|
9900
10150
|
pkgList.push({
|
|
9901
|
-
group,
|
|
9902
|
-
name,
|
|
9903
|
-
version,
|
|
9904
|
-
|
|
9905
|
-
|
|
10151
|
+
group: "",
|
|
10152
|
+
name: pkgCmd.name,
|
|
10153
|
+
version: pkgCmd.version || undefined,
|
|
10154
|
+
scope: "excluded",
|
|
10155
|
+
"bom-ref": uuidv4(),
|
|
10156
|
+
description: key,
|
|
10157
|
+
properties: [
|
|
10158
|
+
{ name: "SrcFile", value: f },
|
|
10159
|
+
{ name: "cdx:github:workflow:name", value: workflowName },
|
|
10160
|
+
{ name: "cdx:github:job:name", value: jobName },
|
|
10161
|
+
{ name: "cdx:github:step:type", value: "run" },
|
|
10162
|
+
{ name: "cdx:github:step:command", value: pkgCmd.command },
|
|
10163
|
+
{ name: "cdx:github:run:line", value: runLine.toString() },
|
|
10164
|
+
],
|
|
9906
10165
|
evidence: {
|
|
9907
10166
|
identity: {
|
|
9908
10167
|
field: "purl",
|
|
9909
|
-
confidence,
|
|
10168
|
+
confidence: 0.5,
|
|
9910
10169
|
methods: [
|
|
9911
10170
|
{
|
|
9912
10171
|
technique: "source-code-analysis",
|
|
9913
|
-
confidence,
|
|
10172
|
+
confidence: 0.5,
|
|
9914
10173
|
value: f,
|
|
9915
10174
|
},
|
|
9916
10175
|
],
|
|
@@ -9925,6 +10184,103 @@ export function parseGitHubWorkflowData(f) {
|
|
|
9925
10184
|
return pkgList;
|
|
9926
10185
|
}
|
|
9927
10186
|
|
|
10187
|
+
/**
|
|
10188
|
+
* Analyze permissions for write access.
|
|
10189
|
+
*
|
|
10190
|
+
* Refer to https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#permissions
|
|
10191
|
+
*/
|
|
10192
|
+
function analyzePermissions(permissions) {
|
|
10193
|
+
if (!permissions || typeof permissions !== "object") {
|
|
10194
|
+
return false;
|
|
10195
|
+
}
|
|
10196
|
+
const writePermissions = [
|
|
10197
|
+
"actions",
|
|
10198
|
+
"artifact-metadata",
|
|
10199
|
+
"attestations",
|
|
10200
|
+
"checks",
|
|
10201
|
+
"contents",
|
|
10202
|
+
"deployments",
|
|
10203
|
+
"id-token",
|
|
10204
|
+
"models",
|
|
10205
|
+
"discussions",
|
|
10206
|
+
"packages",
|
|
10207
|
+
"pages",
|
|
10208
|
+
"actions",
|
|
10209
|
+
"deployments",
|
|
10210
|
+
"issues",
|
|
10211
|
+
"pull-requests",
|
|
10212
|
+
"security-events",
|
|
10213
|
+
"statuses",
|
|
10214
|
+
];
|
|
10215
|
+
for (const perm of writePermissions) {
|
|
10216
|
+
if (permissions[perm] === "write") {
|
|
10217
|
+
return true;
|
|
10218
|
+
}
|
|
10219
|
+
}
|
|
10220
|
+
return false;
|
|
10221
|
+
}
|
|
10222
|
+
|
|
10223
|
+
/**
|
|
10224
|
+
* Determine version pinning type for security assessment
|
|
10225
|
+
*/
|
|
10226
|
+
function getVersionPinningType(versionRef) {
|
|
10227
|
+
if (!versionRef) {
|
|
10228
|
+
return "unknown";
|
|
10229
|
+
}
|
|
10230
|
+
if (/^[a-f0-9]{40}$/.test(versionRef)) {
|
|
10231
|
+
return "sha";
|
|
10232
|
+
}
|
|
10233
|
+
if (/^[a-f0-9]{7,}$/.test(versionRef)) {
|
|
10234
|
+
return "sha";
|
|
10235
|
+
}
|
|
10236
|
+
if (
|
|
10237
|
+
versionRef === "main" ||
|
|
10238
|
+
versionRef === "master" ||
|
|
10239
|
+
versionRef.includes("/")
|
|
10240
|
+
) {
|
|
10241
|
+
return "branch";
|
|
10242
|
+
}
|
|
10243
|
+
return "tag";
|
|
10244
|
+
}
|
|
10245
|
+
|
|
10246
|
+
/**
|
|
10247
|
+
* Extract package manager commands from run steps
|
|
10248
|
+
*/
|
|
10249
|
+
function extractPackageManagerCommands(runScript) {
|
|
10250
|
+
const commands = [];
|
|
10251
|
+
if (!runScript) {
|
|
10252
|
+
return commands;
|
|
10253
|
+
}
|
|
10254
|
+
const patterns = [
|
|
10255
|
+
{ regex: /npm\s+(install|i|ci)\s+([^&\n|;]+)/g, name: "npm", type: "npm" },
|
|
10256
|
+
{
|
|
10257
|
+
regex: /yarn\s+(add|install)\s+([^&\n|;]+)/g,
|
|
10258
|
+
name: "yarn",
|
|
10259
|
+
type: "yarn",
|
|
10260
|
+
},
|
|
10261
|
+
{ regex: /pip\s+(install)\s+([^&\n|;]+)/g, name: "pip", type: "pip" },
|
|
10262
|
+
{ regex: /pip3\s+(install)\s+([^&\n|;]+)/g, name: "pip3", type: "pip" },
|
|
10263
|
+
{ regex: /gem\s+(install)\s+([^&\n|;]+)/g, name: "gem", type: "gem" },
|
|
10264
|
+
{ regex: /go\s+(get|install)\s+([^&\n|;]+)/g, name: "go", type: "go" },
|
|
10265
|
+
{
|
|
10266
|
+
regex: /cargo\s+(install|add)\s+([^&\n|;]+)/g,
|
|
10267
|
+
name: "cargo",
|
|
10268
|
+
type: "cargo",
|
|
10269
|
+
},
|
|
10270
|
+
];
|
|
10271
|
+
for (const pattern of patterns) {
|
|
10272
|
+
let match;
|
|
10273
|
+
while ((match = pattern.regex.exec(runScript)) !== null) {
|
|
10274
|
+
commands.push({
|
|
10275
|
+
name: pattern.name,
|
|
10276
|
+
command: match[0],
|
|
10277
|
+
version: null,
|
|
10278
|
+
});
|
|
10279
|
+
}
|
|
10280
|
+
}
|
|
10281
|
+
return commands;
|
|
10282
|
+
}
|
|
10283
|
+
|
|
9928
10284
|
export function parseCloudBuildData(cbwData) {
|
|
9929
10285
|
const pkgList = [];
|
|
9930
10286
|
const keys_cache = {};
|
|
@@ -10791,6 +11147,9 @@ export function parseCsPkgData(pkgData, pkgFile) {
|
|
|
10791
11147
|
export function getPropertyGroupTextNodes(propsFiles) {
|
|
10792
11148
|
const matches = {};
|
|
10793
11149
|
for (const f of propsFiles) {
|
|
11150
|
+
if (!f) {
|
|
11151
|
+
continue;
|
|
11152
|
+
}
|
|
10794
11153
|
let projects;
|
|
10795
11154
|
try {
|
|
10796
11155
|
const data = readFileSync(f, { encoding: "utf-8" });
|
|
@@ -11581,9 +11940,7 @@ export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
|
11581
11940
|
assetData.dependencies[aversion][adep].resolved;
|
|
11582
11941
|
} else if (
|
|
11583
11942
|
aversion.includes("/") &&
|
|
11584
|
-
assetData.dependencies[aversionNoRuntime]
|
|
11585
|
-
assetData.dependencies[aversionNoRuntime][adep] &&
|
|
11586
|
-
assetData.dependencies[aversionNoRuntime][adep].resolved
|
|
11943
|
+
assetData.dependencies[aversionNoRuntime]?.[adep]?.resolved
|
|
11587
11944
|
) {
|
|
11588
11945
|
adepResolvedVersion =
|
|
11589
11946
|
assetData.dependencies[aversionNoRuntime][adep].resolved;
|
|
@@ -12653,7 +13010,7 @@ export async function collectMvnDependencies(
|
|
|
12653
13010
|
}
|
|
12654
13011
|
|
|
12655
13012
|
// Clean up
|
|
12656
|
-
if (cleanup && tempDir
|
|
13013
|
+
if (cleanup && tempDir?.startsWith(tmpdir()) && rmSync) {
|
|
12657
13014
|
rmSync(tempDir, { recursive: true, force: true });
|
|
12658
13015
|
}
|
|
12659
13016
|
return jarNSMapping;
|
|
@@ -15014,7 +15371,11 @@ export async function getPipFrozenTree(
|
|
|
15014
15371
|
thoughtLog(
|
|
15015
15372
|
"Let me create a new virtual environment for installing the packages with pip.",
|
|
15016
15373
|
);
|
|
15017
|
-
|
|
15374
|
+
const venvCreationArgs = ["-m", "venv", tempVenvDir];
|
|
15375
|
+
if (isSecureMode) {
|
|
15376
|
+
venvCreationArgs.unshift("-S");
|
|
15377
|
+
}
|
|
15378
|
+
result = safeSpawnSync(PYTHON_CMD, venvCreationArgs, {
|
|
15018
15379
|
shell: isWin,
|
|
15019
15380
|
});
|
|
15020
15381
|
if (result.status !== 0 || result.error) {
|
|
@@ -15065,12 +15426,18 @@ export async function getPipFrozenTree(
|
|
|
15065
15426
|
"true",
|
|
15066
15427
|
"--local",
|
|
15067
15428
|
];
|
|
15429
|
+
if (isSecureMode) {
|
|
15430
|
+
poetryConfigArgs.unshift("-S");
|
|
15431
|
+
}
|
|
15068
15432
|
result = safeSpawnSync(PYTHON_CMD, poetryConfigArgs, {
|
|
15069
15433
|
cwd: basePath,
|
|
15070
15434
|
shell: isWin,
|
|
15071
15435
|
});
|
|
15072
15436
|
thoughtLog("Performing poetry install");
|
|
15073
15437
|
let poetryInstallArgs = ["-m", "poetry", "install", "-n", "--no-root"];
|
|
15438
|
+
if (isSecureMode) {
|
|
15439
|
+
poetryInstallArgs.unshift("-S");
|
|
15440
|
+
}
|
|
15074
15441
|
// Attempt to perform poetry install
|
|
15075
15442
|
result = safeSpawnSync(PYTHON_CMD, poetryInstallArgs, {
|
|
15076
15443
|
cwd: basePath,
|
|
@@ -15147,6 +15514,9 @@ export async function getPipFrozenTree(
|
|
|
15147
15514
|
"install",
|
|
15148
15515
|
"--disable-pip-version-check",
|
|
15149
15516
|
];
|
|
15517
|
+
if (isSecureMode) {
|
|
15518
|
+
pipInstallArgs.unshift("-S");
|
|
15519
|
+
}
|
|
15150
15520
|
// Requirements.txt could be called with any name so best to check for not setup.py and not pyproject.toml
|
|
15151
15521
|
if (
|
|
15152
15522
|
!reqOrSetupFile.endsWith("setup.py") &&
|
|
@@ -15467,7 +15837,11 @@ export function getPipTreeForPackages(
|
|
|
15467
15837
|
};
|
|
15468
15838
|
if (!process.env.VIRTUAL_ENV && !process.env.CONDA_PREFIX) {
|
|
15469
15839
|
// Create a virtual environment
|
|
15470
|
-
|
|
15840
|
+
const venvCreationArgs = ["-m", "venv", tempVenvDir];
|
|
15841
|
+
if (isSecureMode) {
|
|
15842
|
+
venvCreationArgs.unshift("-S");
|
|
15843
|
+
}
|
|
15844
|
+
result = safeSpawnSync(PYTHON_CMD, venvCreationArgs, {
|
|
15471
15845
|
shell: isWin,
|
|
15472
15846
|
});
|
|
15473
15847
|
if (result.status !== 0 || result.error) {
|
|
@@ -15490,6 +15864,9 @@ export function getPipTreeForPackages(
|
|
|
15490
15864
|
}
|
|
15491
15865
|
const python_cmd_for_tree = get_python_command_from_env(env);
|
|
15492
15866
|
let pipInstallArgs = ["-m", "pip", "install", "--disable-pip-version-check"];
|
|
15867
|
+
if (isSecureMode) {
|
|
15868
|
+
pipInstallArgs.unshift("-S");
|
|
15869
|
+
}
|
|
15493
15870
|
// Support for passing additional arguments to pip
|
|
15494
15871
|
// Eg: --python-version 3.10 --ignore-requires-python --no-warn-conflicts
|
|
15495
15872
|
if (process?.env?.PIP_INSTALL_ARGS) {
|
|
@@ -16059,29 +16436,24 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
|
16059
16436
|
}
|
|
16060
16437
|
}
|
|
16061
16438
|
} else if (l.includes("dependency(")) {
|
|
16062
|
-
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
if (
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
) {
|
|
16081
|
-
// We have a valid version
|
|
16082
|
-
versionsMap[tmpA[0]] = tmpB[1];
|
|
16083
|
-
}
|
|
16084
|
-
}
|
|
16439
|
+
if (!l.includes("_dependency") && !l.includes(".dependency")) {
|
|
16440
|
+
const depMatch = l.match(/dependency\(\s*['"]?([^'",)\s]+)['"]?/);
|
|
16441
|
+
const depName = depMatch?.[1]?.trim();
|
|
16442
|
+
if (depName) {
|
|
16443
|
+
name_list.push(depName);
|
|
16444
|
+
const versionMatch = l.match(/version\s*:\s*['"]?([^'",)\s]+)['"]?/);
|
|
16445
|
+
const depVersion = versionMatch?.[1]?.trim();
|
|
16446
|
+
if (depVersion) {
|
|
16447
|
+
if (depVersion.includes(">") || depVersion.includes("<")) {
|
|
16448
|
+
// We have a version specifier
|
|
16449
|
+
versionSpecifiersMap[depName] = depVersion;
|
|
16450
|
+
} else if (
|
|
16451
|
+
/^\d+/.test(depVersion) &&
|
|
16452
|
+
!depVersion.includes("${") &&
|
|
16453
|
+
!depVersion.startsWith("@")
|
|
16454
|
+
) {
|
|
16455
|
+
// We have a valid version
|
|
16456
|
+
versionsMap[depName] = depVersion;
|
|
16085
16457
|
}
|
|
16086
16458
|
}
|
|
16087
16459
|
}
|