@cyclonedx/cdxgen 12.1.2 → 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.
Files changed (35) hide show
  1. package/README.md +10 -8
  2. package/bin/cdxgen.js +1 -1
  3. package/lib/cli/index.js +2 -2
  4. package/lib/evinser/evinser.js +2 -8
  5. package/lib/helpers/display.js +1 -1
  6. package/lib/helpers/envcontext.js +10 -2
  7. package/lib/helpers/utils.js +421 -86
  8. package/lib/helpers/utils.poku.js +179 -2
  9. package/lib/helpers/validator.js +8 -5
  10. package/lib/managers/docker.getConnection.poku.js +61 -0
  11. package/lib/managers/docker.js +36 -23
  12. package/lib/parsers/iri.js +1 -2
  13. package/lib/server/server.js +164 -34
  14. package/lib/server/server.poku.js +232 -10
  15. package/lib/stages/postgen/annotator.js +281 -3
  16. package/lib/stages/postgen/postgen.js +4 -7
  17. package/lib/third-party/arborist/lib/diff.js +1 -1
  18. package/lib/third-party/arborist/lib/node.js +1 -1
  19. package/lib/third-party/arborist/lib/yarn-lock.js +1 -1
  20. package/package.json +20 -326
  21. package/types/bin/dependencies.d.ts.map +1 -1
  22. package/types/lib/evinser/evinser.d.ts.map +1 -1
  23. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  24. package/types/lib/helpers/utils.d.ts.map +1 -1
  25. package/types/lib/helpers/validator.d.ts.map +1 -1
  26. package/types/lib/managers/docker.d.ts.map +1 -1
  27. package/types/lib/parsers/iri.d.ts.map +1 -1
  28. package/types/lib/server/server.d.ts +14 -0
  29. package/types/lib/server/server.d.ts.map +1 -1
  30. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  31. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  32. package/bin/dependencies.js +0 -131
  33. package/bin/licenses.js +0 -78
  34. package/lib/helpers/dependencies.poku.js +0 -11
  35. package/lib/helpers/licenses.poku.js +0 -11
@@ -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 && pkgList.length) {
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 && pkgList.length) {
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 && pkgList.length) {
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 && pkgList.length) {
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 && pkgList.length) {
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 && pkgList.length) {
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 && pkgList.length) {
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",
@@ -9877,82 +9926,250 @@ export function parseGitHubWorkflowData(f) {
9877
9926
  return pkgList;
9878
9927
  }
9879
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";
9880
9945
  for (const jobName of Object.keys(yamlObj.jobs)) {
9881
- if (yamlObj.jobs[jobName].steps) {
9882
- for (const step of yamlObj.jobs[jobName].steps) {
9883
- if (step.uses) {
9884
- const tmpA = step.uses.split("@");
9885
- if (tmpA.length !== 2) {
9886
- continue;
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];
9887
9996
  }
9888
- const groupName = tmpA[0];
9889
- let name = groupName;
9890
- let group = "";
9891
- const tagOrCommit = tmpA[1];
9892
- let version = tagOrCommit;
9893
- const tmpB = groupName.split("/");
9894
- if (tmpB.length >= 2) {
9895
- name = tmpB.pop();
9896
- group = tmpB.join("/");
9897
- } else if (tmpB.length === 1) {
9898
- name = tmpB[0];
9899
- 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}`;
9900
10005
  }
9901
- let lineNum = -1;
9902
- const stepLineMatch = ghwData.indexOf(step.uses);
9903
- if (stepLineMatch >= 0) {
9904
- lineNum =
9905
- ghwData.substring(0, stepLineMatch).split("\n").length - 1;
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;
9906
10013
  }
9907
- if (lineNum >= 0 && lines[lineNum]) {
9908
- const line = lines[lineNum];
9909
- const commentMatch = line.match(/#\s*v?([0-9]+(?:\.[0-9]+)*)/);
9910
- if (commentMatch?.[1]) {
9911
- version = commentMatch[1];
9912
- }
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 });
9913
10034
  }
9914
- const key = `${group}-${name}-${version}`;
9915
- let confidence = 0.6;
9916
- if (!keys_cache[key] && name && version) {
9917
- keys_cache[key] = key;
9918
- let fullName = name;
9919
- if (group.length) {
9920
- fullName = `${group}/${name}`;
9921
- }
9922
- let purl = `pkg:github/${fullName}@${version}`;
9923
- if (tagOrCommit && version !== tagOrCommit) {
9924
- const qualifierDesc = tagOrCommit.startsWith("v")
9925
- ? "tag"
9926
- : "commit";
9927
- purl = `${purl}?${qualifierDesc}=${tagOrCommit}`;
9928
- confidence = 0.7;
9929
- }
9930
- const properties = [
9931
- {
9932
- name: "SrcFile",
9933
- value: f,
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
+ ],
9934
10134
  },
9935
- ];
9936
- if (group?.startsWith("github/") || group === "actions") {
9937
- properties.push({
9938
- name: "cdx:actions:isOfficial",
9939
- value: "true",
9940
- });
9941
- }
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;
9942
10150
  pkgList.push({
9943
- group,
9944
- name,
9945
- version,
9946
- purl,
9947
- properties,
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
+ ],
9948
10165
  evidence: {
9949
10166
  identity: {
9950
10167
  field: "purl",
9951
- confidence,
10168
+ confidence: 0.5,
9952
10169
  methods: [
9953
10170
  {
9954
10171
  technique: "source-code-analysis",
9955
- confidence,
10172
+ confidence: 0.5,
9956
10173
  value: f,
9957
10174
  },
9958
10175
  ],
@@ -9967,6 +10184,103 @@ export function parseGitHubWorkflowData(f) {
9967
10184
  return pkgList;
9968
10185
  }
9969
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
+
9970
10284
  export function parseCloudBuildData(cbwData) {
9971
10285
  const pkgList = [];
9972
10286
  const keys_cache = {};
@@ -10833,6 +11147,9 @@ export function parseCsPkgData(pkgData, pkgFile) {
10833
11147
  export function getPropertyGroupTextNodes(propsFiles) {
10834
11148
  const matches = {};
10835
11149
  for (const f of propsFiles) {
11150
+ if (!f) {
11151
+ continue;
11152
+ }
10836
11153
  let projects;
10837
11154
  try {
10838
11155
  const data = readFileSync(f, { encoding: "utf-8" });
@@ -11623,9 +11940,7 @@ export function parseCsPkgLockData(csLockData, pkgLockFile) {
11623
11940
  assetData.dependencies[aversion][adep].resolved;
11624
11941
  } else if (
11625
11942
  aversion.includes("/") &&
11626
- assetData.dependencies[aversionNoRuntime] &&
11627
- assetData.dependencies[aversionNoRuntime][adep] &&
11628
- assetData.dependencies[aversionNoRuntime][adep].resolved
11943
+ assetData.dependencies[aversionNoRuntime]?.[adep]?.resolved
11629
11944
  ) {
11630
11945
  adepResolvedVersion =
11631
11946
  assetData.dependencies[aversionNoRuntime][adep].resolved;
@@ -12695,7 +13010,7 @@ export async function collectMvnDependencies(
12695
13010
  }
12696
13011
 
12697
13012
  // Clean up
12698
- if (cleanup && tempDir && tempDir.startsWith(tmpdir()) && rmSync) {
13013
+ if (cleanup && tempDir?.startsWith(tmpdir()) && rmSync) {
12699
13014
  rmSync(tempDir, { recursive: true, force: true });
12700
13015
  }
12701
13016
  return jarNSMapping;
@@ -15056,7 +15371,11 @@ export async function getPipFrozenTree(
15056
15371
  thoughtLog(
15057
15372
  "Let me create a new virtual environment for installing the packages with pip.",
15058
15373
  );
15059
- result = safeSpawnSync(PYTHON_CMD, ["-m", "venv", tempVenvDir], {
15374
+ const venvCreationArgs = ["-m", "venv", tempVenvDir];
15375
+ if (isSecureMode) {
15376
+ venvCreationArgs.unshift("-S");
15377
+ }
15378
+ result = safeSpawnSync(PYTHON_CMD, venvCreationArgs, {
15060
15379
  shell: isWin,
15061
15380
  });
15062
15381
  if (result.status !== 0 || result.error) {
@@ -15107,12 +15426,18 @@ export async function getPipFrozenTree(
15107
15426
  "true",
15108
15427
  "--local",
15109
15428
  ];
15429
+ if (isSecureMode) {
15430
+ poetryConfigArgs.unshift("-S");
15431
+ }
15110
15432
  result = safeSpawnSync(PYTHON_CMD, poetryConfigArgs, {
15111
15433
  cwd: basePath,
15112
15434
  shell: isWin,
15113
15435
  });
15114
15436
  thoughtLog("Performing poetry install");
15115
15437
  let poetryInstallArgs = ["-m", "poetry", "install", "-n", "--no-root"];
15438
+ if (isSecureMode) {
15439
+ poetryInstallArgs.unshift("-S");
15440
+ }
15116
15441
  // Attempt to perform poetry install
15117
15442
  result = safeSpawnSync(PYTHON_CMD, poetryInstallArgs, {
15118
15443
  cwd: basePath,
@@ -15189,6 +15514,9 @@ export async function getPipFrozenTree(
15189
15514
  "install",
15190
15515
  "--disable-pip-version-check",
15191
15516
  ];
15517
+ if (isSecureMode) {
15518
+ pipInstallArgs.unshift("-S");
15519
+ }
15192
15520
  // Requirements.txt could be called with any name so best to check for not setup.py and not pyproject.toml
15193
15521
  if (
15194
15522
  !reqOrSetupFile.endsWith("setup.py") &&
@@ -15509,7 +15837,11 @@ export function getPipTreeForPackages(
15509
15837
  };
15510
15838
  if (!process.env.VIRTUAL_ENV && !process.env.CONDA_PREFIX) {
15511
15839
  // Create a virtual environment
15512
- result = safeSpawnSync(PYTHON_CMD, ["-m", "venv", tempVenvDir], {
15840
+ const venvCreationArgs = ["-m", "venv", tempVenvDir];
15841
+ if (isSecureMode) {
15842
+ venvCreationArgs.unshift("-S");
15843
+ }
15844
+ result = safeSpawnSync(PYTHON_CMD, venvCreationArgs, {
15513
15845
  shell: isWin,
15514
15846
  });
15515
15847
  if (result.status !== 0 || result.error) {
@@ -15532,6 +15864,9 @@ export function getPipTreeForPackages(
15532
15864
  }
15533
15865
  const python_cmd_for_tree = get_python_command_from_env(env);
15534
15866
  let pipInstallArgs = ["-m", "pip", "install", "--disable-pip-version-check"];
15867
+ if (isSecureMode) {
15868
+ pipInstallArgs.unshift("-S");
15869
+ }
15535
15870
  // Support for passing additional arguments to pip
15536
15871
  // Eg: --python-version 3.10 --ignore-requires-python --no-warn-conflicts
15537
15872
  if (process?.env?.PIP_INSTALL_ARGS) {