@coana-tech/cli 14.12.71 → 14.12.73

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/cli.mjs CHANGED
@@ -205801,9 +205801,13 @@ async function fetchManifestFilesFromManifestsTarHash(manifestsTarHash) {
205801
205801
  throw new Error("we should never reach this point");
205802
205802
  }
205803
205803
  }
205804
- async function fetchArtifactsFromManifestsTarHash(manifestsTarHash) {
205804
+ async function fetchArtifactsFromManifestsTarHash(manifestsTarHash, includePrecomputedReachabilityResults) {
205805
205805
  try {
205806
- const url2 = getSocketApiUrl(`orgs/${process.env.SOCKET_ORG_SLUG}/compute-artifacts?tarHash=${manifestsTarHash}`);
205806
+ const params = new URLSearchParams({
205807
+ tarHash: manifestsTarHash,
205808
+ includePrecomputedReachabilityResults: String(includePrecomputedReachabilityResults ?? false)
205809
+ });
205810
+ const url2 = getSocketApiUrl(`orgs/${process.env.SOCKET_ORG_SLUG}/compute-artifacts?${params.toString()}`);
205807
205811
  const responseData = (await axios2.post(url2, {}, { headers: getAuthHeaders() })).data;
205808
205812
  return parseComputeArtifactsResponse(responseData);
205809
205813
  } catch (e) {
@@ -228738,6 +228742,9 @@ function vulnerabilitiesDiff(oldVulnerabilities, newVulnerabilities, dismissedVu
228738
228742
  // ../web-compat-utils/src/vulnerability-reachability.ts
228739
228743
  function getVulnReachability(c3) {
228740
228744
  if (c3.type === "noAnalysisCheck") return "REACHABLE";
228745
+ if (c3.type === "precomputed") {
228746
+ return c3.results.type === "unreachable" ? "UNREACHABLE" : "UNKNOWN";
228747
+ }
228741
228748
  if (c3.type !== "success") {
228742
228749
  return "UNKNOWN";
228743
228750
  }
@@ -229736,6 +229743,29 @@ ${vulnerabilityFixes.map((fix) => ` ${fix.dependencyName} from ${fix.currentVers
229736
229743
  // dist/internal/socket-mode-helpers-socket-dependency-trees.js
229737
229744
  import { basename as basename7, dirname as dirname21, join as join24, sep as sep5 } from "path";
229738
229745
  var REQUIREMENTS_FILES_SEARCH_DEPTH2 = 3;
229746
+ var venvExcludes = [
229747
+ "venv",
229748
+ ".venv",
229749
+ "env",
229750
+ ".env",
229751
+ "virtualenv",
229752
+ ".virtualenv",
229753
+ "venvs",
229754
+ ".venvs",
229755
+ "envs",
229756
+ ".envs",
229757
+ "__pycache__",
229758
+ ".tox",
229759
+ ".nox",
229760
+ ".pytest_cache",
229761
+ "site-packages",
229762
+ "dist-packages",
229763
+ "conda-meta",
229764
+ "conda-bld",
229765
+ ".mypy_cache",
229766
+ ".ruff_cache",
229767
+ ".hypothesis"
229768
+ ];
229739
229769
  function inferWorkspaceFromManifestPath(ecosystem, manifestPath, properPythonProjects) {
229740
229770
  switch (ecosystem) {
229741
229771
  case "NPM": {
@@ -229747,6 +229777,9 @@ function inferWorkspaceFromManifestPath(ecosystem, manifestPath, properPythonPro
229747
229777
  return ".";
229748
229778
  }
229749
229779
  case "PIP": {
229780
+ if (venvExcludes.some((exclude) => manifestPath.startsWith(`${exclude}/`) || manifestPath.includes(`/${exclude}/`))) {
229781
+ return void 0;
229782
+ }
229750
229783
  const base = basename7(manifestPath);
229751
229784
  const dir = dirname21(manifestPath);
229752
229785
  const workspaceDir = dir === "" ? "." : dir;
@@ -229819,11 +229852,11 @@ function getAllToplevelAncestors(artifactMap, artifactId) {
229819
229852
  findAncestors(artifactId);
229820
229853
  return Array.from(toplevelAncestors);
229821
229854
  }
229822
- async function fetchArtifactsFromSocket(rootWorkingDirectory, manifestsTarHash, mode) {
229855
+ async function fetchArtifactsFromSocket(rootWorkingDirectory, manifestsTarHash, mode, includePrecomputedReachabilityResults) {
229823
229856
  logger.info("Fetching artifacts from Socket backend (this may take a while)");
229824
229857
  logger.debug("manifests tar hash", manifestsTarHash);
229825
229858
  try {
229826
- const { artifacts } = await fetchArtifactsFromManifestsTarHash(manifestsTarHash);
229859
+ const { artifacts } = await fetchArtifactsFromManifestsTarHash(manifestsTarHash, includePrecomputedReachabilityResults);
229827
229860
  const properPythonProjects = [];
229828
229861
  const pipArtifactToRepresentativeManifest = {};
229829
229862
  for (const artifact of artifacts) {
@@ -229831,29 +229864,6 @@ async function fetchArtifactsFromSocket(rootWorkingDirectory, manifestsTarHash,
229831
229864
  pipArtifactToRepresentativeManifest[simplePurl(artifact.type, artifact.namespace ?? "", artifact.name ?? "", artifact.version ?? "")] = artifact;
229832
229865
  }
229833
229866
  }
229834
- const venvExcludes = [
229835
- "venv",
229836
- ".venv",
229837
- "env",
229838
- ".env",
229839
- "virtualenv",
229840
- ".virtualenv",
229841
- "venvs",
229842
- ".venvs",
229843
- "envs",
229844
- ".envs",
229845
- "__pycache__",
229846
- ".tox",
229847
- ".nox",
229848
- ".pytest_cache",
229849
- "site-packages",
229850
- "dist-packages",
229851
- "conda-meta",
229852
- "conda-bld",
229853
- ".mypy_cache",
229854
- ".ruff_cache",
229855
- ".hypothesis"
229856
- ];
229857
229867
  const allFiles = await getFilesRelative(rootWorkingDirectory, venvExcludes);
229858
229868
  for (const file of allFiles) {
229859
229869
  const base = basename7(file);
@@ -229896,7 +229906,7 @@ async function fetchArtifactsFromSocket(rootWorkingDirectory, manifestsTarHash,
229896
229906
  break;
229897
229907
  }
229898
229908
  case "PIP": {
229899
- const sPurl = simplePurl(artifact.type, artifact.namespace ?? "", artifact.name ?? "", artifact.version ?? "");
229909
+ const sPurl = simplePurl(artifact.type, artifact.namespace ?? null, artifact.name ?? null, artifact.version ?? null);
229900
229910
  if (pipArtifactToRepresentativeManifest[sPurl]) {
229901
229911
  manifestFiles.push(...(pipArtifactToRepresentativeManifest[sPurl].manifestFiles ?? []).map((ref) => ref.file));
229902
229912
  }
@@ -229925,7 +229935,7 @@ async function fetchArtifactsFromSocket(rootWorkingDirectory, manifestsTarHash,
229925
229935
  });
229926
229936
  }
229927
229937
  if (Object.keys(workspaceToManifestFiles).length === 0 && artifact.vulnerabilities && artifact.vulnerabilities.length > 0) {
229928
- purlsFailedToFindWorkspace.add(purlToString(artifact));
229938
+ purlsFailedToFindWorkspace.add(simplePurl(artifact.type, artifact.namespace ?? null, artifact.name ?? null, artifact.version ?? null));
229929
229939
  }
229930
229940
  for (const [workspace, manifestFiles2] of Object.entries(workspaceToManifestFiles)) {
229931
229941
  const workspaceData = (ecosystemToWorkspaceToAnalysisData[ecosystem] ??= {})[workspace] ??= {
@@ -229952,7 +229962,8 @@ async function fetchArtifactsFromSocket(rootWorkingDirectory, manifestsTarHash,
229952
229962
  vulnChainDetails: computeVulnChainDetails(artifacts, artifact.id),
229953
229963
  vulnerabilityAccessPaths: vuln.reachabilityData?.undeterminableReachability ? vuln.reachabilityData.publicComment ?? "" : vuln.reachabilityData?.pattern ?? null,
229954
229964
  ecosystem,
229955
- artifactId: artifact.id
229965
+ artifactId: artifact.id,
229966
+ precomputedReachabilityResult: vuln.reachabilityData?.precomputedReachabilityResult ?? null
229956
229967
  };
229957
229968
  const vulnId = `${ecosystem}-${workspace}-${artifact.namespace}-${artifact.name}-${artifact.version}-${vulnerability.url}`;
229958
229969
  if (!ecosystemWorkspaceVulnIds.has(vulnId)) {
@@ -230455,6 +230466,16 @@ function isShortestPath(root3, vulnPath) {
230455
230466
  // ../web-compat-utils/src/analysis-error-keys.ts
230456
230467
  var CLI_ANALYSIS_ERROR_MESSAGE = "Sharing log due to analysis error";
230457
230468
 
230469
+ // ../web-compat-utils/src/pluralize.ts
230470
+ function pluralize(count, word) {
230471
+ if (word === "vulnerability") {
230472
+ return count === 1 ? "vulnerability" : "vulnerabilities";
230473
+ } else {
230474
+ word;
230475
+ throw new Error(`Invalid word: ${word}`);
230476
+ }
230477
+ }
230478
+
230458
230479
  // ../web-compat-utils/src/package-utils.ts
230459
230480
  function extractNameAndVersion(packageNameAtVersion) {
230460
230481
  const atNpmIdx = packageNameAtVersion.indexOf("@npm:");
@@ -230872,11 +230893,15 @@ function toSocketFacts(report, dependencyTrees, subPjToWsPathToDirectDependencie
230872
230893
  };
230873
230894
  component.reachability.push(reachabilityForGHSA);
230874
230895
  }
230875
- reachabilityForGHSA.reachability.push({
230876
- ...toSocketReachabilitySchema(vulnerability),
230877
- workspacePath: vulnerability.workspacePath,
230878
- subprojectPath: vulnerability.subprojectPath
230879
- });
230896
+ if (vulnerability.codeAwareScanResult.type === "precomputed") {
230897
+ reachabilityForGHSA.reachability.push(vulnerability.codeAwareScanResult);
230898
+ } else {
230899
+ reachabilityForGHSA.reachability.push({
230900
+ ...toSocketReachabilitySchema(vulnerability),
230901
+ workspacePath: vulnerability.workspacePath,
230902
+ subprojectPath: vulnerability.subprojectPath
230903
+ });
230904
+ }
230880
230905
  }
230881
230906
  return {
230882
230907
  components
@@ -230955,11 +230980,15 @@ function toSocketFactsSocketDependencyTree(artifacts, vulnerabilities, tier1Reac
230955
230980
  };
230956
230981
  component.reachability.push(reachabilityForGHSA);
230957
230982
  }
230958
- reachabilityForGHSA.reachability.push({
230959
- ...toSocketReachabilitySchema(vulnerability),
230960
- workspacePath: vulnerability.workspacePath,
230961
- subprojectPath: vulnerability.subprojectPath
230962
- });
230983
+ if (vulnerability.codeAwareScanResult.type === "precomputed") {
230984
+ reachabilityForGHSA.reachability.push(vulnerability.codeAwareScanResult);
230985
+ } else {
230986
+ reachabilityForGHSA.reachability.push({
230987
+ ...toSocketReachabilitySchema(vulnerability),
230988
+ workspacePath: vulnerability.workspacePath,
230989
+ subprojectPath: vulnerability.subprojectPath
230990
+ });
230991
+ }
230963
230992
  }
230964
230993
  return {
230965
230994
  components: artifacts,
@@ -245855,7 +245884,7 @@ async function onlineScan(dependencyTree, apiKey, timeout) {
245855
245884
  }
245856
245885
 
245857
245886
  // dist/version.js
245858
- var version2 = "14.12.71";
245887
+ var version2 = "14.12.73";
245859
245888
 
245860
245889
  // dist/cli-core.js
245861
245890
  var { mapValues, omit, partition, pick } = import_lodash15.default;
@@ -246017,7 +246046,7 @@ var CliCore = class {
246017
246046
  async computeAndOutputReportSocketMode(otherModulesCommunicator) {
246018
246047
  logger.info("Fetching artifacts from Socket backend");
246019
246048
  this.sendProgress("SCAN_FOR_VULNERABILITIES", true, ".", ".");
246020
- const { artifacts, ecosystemToWorkspaceToAnalysisData, ecosystemToWorkspaceToVulnerabilities } = await fetchArtifactsFromSocket(this.rootWorkingDirectory, this.options.manifestsTarHash, "reachability");
246049
+ const { artifacts, ecosystemToWorkspaceToAnalysisData, ecosystemToWorkspaceToVulnerabilities } = await fetchArtifactsFromSocket(this.rootWorkingDirectory, this.options.manifestsTarHash, "reachability", this.options.useUnreachableFromPrecomputation);
246021
246050
  this.sendProgress("SCAN_FOR_VULNERABILITIES", false, ".", ".");
246022
246051
  const subProjects = Object.entries(ecosystemToWorkspaceToAnalysisData).flatMap(([ecosystem, workspaceToAnalysisData]) => {
246023
246052
  return Object.entries(workspaceToAnalysisData).map(([workspace, analysisData]) => {
@@ -246242,7 +246271,9 @@ Subproject: ${subproject}`);
246242
246271
  "timeout",
246243
246272
  "analysisTimeout",
246244
246273
  "concurrency",
246245
- "excludeDirs"
246274
+ "excludeDirs",
246275
+ "useUnreachableFromPrecomputation",
246276
+ "minSeverity"
246246
246277
  ]), null, 2));
246247
246278
  logger.info("available memory: %i MiB", os.freemem() / (1024 * 1024));
246248
246279
  try {
@@ -246320,9 +246351,11 @@ Subproject: ${subproject}`);
246320
246351
  const workspaceToAugmentedVulnerabilities = Object.fromEntries(await asyncMap(workspaces, async (workspacePath, index2) => {
246321
246352
  analysisStarting?.(workspacePath, index2 + 1, totalWorkspaces);
246322
246353
  const vulnerabilities = workspaceToVulnerabilities[workspacePath] ?? [];
246354
+ logger.info(`Process workspace ${workspacePath} with ${vulnerabilities.length} ${pluralize(vulnerabilities.length, "vulnerability")}`);
246323
246355
  try {
246324
246356
  const dataForAnalysis = workspacePathToDataForAnalysis[workspacePath];
246325
- const [vulnerabilitiesToAnalyze, vulnerabilitiesBelowThreshold] = this.options.minSeverity ? partition(vulnerabilities, (v) => !v.severity || shouldAnalyzeBasedOnSeverity(v.severity, this.options.minSeverity)) : [vulnerabilities, []];
246357
+ const [vulnsSatisfyingThreshold, vulnerabilitiesBelowThreshold] = this.options.minSeverity ? partition(vulnerabilities, (v) => !v.severity || shouldAnalyzeBasedOnSeverity(v.severity, this.options.minSeverity)) : [vulnerabilities, []];
246358
+ const [vulnsUnreachableFromPrecomputation, vulnerabilitiesToAnalyze] = this.options.useUnreachableFromPrecomputation ? partition(vulnsSatisfyingThreshold, (v) => "precomputedReachabilityResult" in v && v.precomputedReachabilityResult?.type === "unreachable") : [[], vulnsSatisfyingThreshold];
246326
246359
  const vulnerabilitiesBelowThresholdWithResults = vulnerabilitiesBelowThreshold.map((v) => ({
246327
246360
  ...v,
246328
246361
  results: {
@@ -246330,10 +246363,23 @@ Subproject: ${subproject}`);
246330
246363
  message: `Reachability analysis not run since the severity of the vulnerability (${v.severity}) is lower than the min severity threshold: ${this.options.minSeverity}`
246331
246364
  }
246332
246365
  }));
246366
+ const vulnerabilitiesDeterminedUnreachableFromPrecomputation = vulnsUnreachableFromPrecomputation.map((v) => ({
246367
+ ...v,
246368
+ results: {
246369
+ type: "precomputed",
246370
+ results: v.precomputedReachabilityResult
246371
+ }
246372
+ }));
246333
246373
  if (vulnerabilitiesBelowThreshold.length > 0) {
246334
- logger.info(`Reachability analysis not run for ${vulnerabilitiesBelowThreshold.length} vulnerabilities with severity level ${this.options.minSeverity} in workspace ${workspacePath}`);
246374
+ logger.info(`Reachability analysis skipped for ${vulnerabilitiesBelowThreshold.length} ${pluralize(vulnerabilities.length, "vulnerability")} with severity below the min severity threshold: ${this.options.minSeverity}`);
246375
+ }
246376
+ if (vulnsUnreachableFromPrecomputation.length > 0) {
246377
+ logger.info(`Reachability analysis skipped for ${vulnsUnreachableFromPrecomputation.length} ${pluralize(vulnerabilities.length, "vulnerability")} that ${vulnerabilities.length !== 1 ? "are" : "is"} already known to be unreachable from precomputed (Tier 2) reachability analysis`);
246378
+ }
246379
+ if (vulnerabilitiesToAnalyze.length > 0) {
246380
+ logger.info(`Running reachability analysis for ${vulnerabilitiesToAnalyze.length} ${pluralize(vulnerabilities.length, "vulnerability")}`);
246335
246381
  }
246336
- const augmentedVulnerabilitiesToAnalyze = reachabilitySupported && !this.shouldExcludeAnalyzingWorkspace(subprojectPath, workspacePath) ? await this.runReachabilityAnalysis(otherModulesCommunicator, subprojectPath, workspacePath, dataForAnalysis, ecosystem, vulnerabilitiesToAnalyze) : vulnerabilitiesToAnalyze.map((v) => ({
246382
+ const augmentedVulnerabilitiesToAnalyze = vulnerabilitiesToAnalyze.length === 0 ? [] : reachabilitySupported && !this.shouldExcludeAnalyzingWorkspace(subprojectPath, workspacePath) ? await this.runReachabilityAnalysis(otherModulesCommunicator, subprojectPath, workspacePath, dataForAnalysis, ecosystem, vulnerabilitiesToAnalyze) : vulnerabilitiesToAnalyze.map((v) => ({
246337
246383
  ...v,
246338
246384
  results: {
246339
246385
  type: "otherError",
@@ -246341,6 +246387,7 @@ Subproject: ${subproject}`);
246341
246387
  }
246342
246388
  }));
246343
246389
  const augmentedVulnerabilities = [
246390
+ ...vulnerabilitiesDeterminedUnreachableFromPrecomputation,
246344
246391
  ...augmentedVulnerabilitiesToAnalyze,
246345
246392
  ...vulnerabilitiesBelowThresholdWithResults
246346
246393
  ];
@@ -246427,6 +246474,7 @@ Subproject: ${subproject}`);
246427
246474
  const [vulnsWithActualAPIPatterns, result] = partition(vulnerabilities, (v) => Array.isArray(v.vulnerabilityAccessPaths));
246428
246475
  for (const v of result)
246429
246476
  v.results = typeof v.vulnerabilityAccessPaths === "string" ? { type: "noAnalysisCheck", message: v.vulnerabilityAccessPaths } : { type: "missingVulnerabilityPattern" };
246477
+ logger.info(`Reachability analysis not possible for ${result.length} of the ${pluralize(vulnerabilities.length, "vulnerability")}`);
246430
246478
  if (!vulnsWithActualAPIPatterns.length)
246431
246479
  return result;
246432
246480
  this.sendProgress("REACHABILITY_ANALYSIS", true, subprojectPath, workspacePath);
@@ -246469,7 +246517,7 @@ Subproject: ${subproject}`);
246469
246517
  }
246470
246518
  return {
246471
246519
  vulnerabilityUrl: v.url,
246472
- vulnerabilityUnreachableByPrecomputation: v.unreachableByPrecomputation ? v.unreachableByPrecomputation : "NOT_COMPUTED",
246520
+ vulnerabilityUnreachableByPrecomputation: "NOT_COMPUTED",
246473
246521
  // vulnChainDetails is always present
246474
246522
  // we only keep it as optional (potentially undefined) to
246475
246523
  // handle requests to the backend from old version of the CLI.
@@ -246594,7 +246642,7 @@ async function getGitDataToMetadataIfAvailable(rootWorkingDirectory) {
246594
246642
  // dist/index.js
246595
246643
  var program2 = new Command();
246596
246644
  var run2 = new Command();
246597
- run2.name("run").argument("<path>", "File system path to folder containing the project").option("-o, --output-dir <path>", "Write json report to <path>/coana-report.json").option("-d, --debug", "Enable debug logging", false).option("-s, --silent", "Silence all debug/warning output", false).option("--silent-spinner", "Silence spinner", "CI" in process.env || !process.stdin.isTTY).option("-p, --print-report", "Print the report to the console", false).option("--offline-database <path>", "Path to a coana-offline-db.json file for running the CLI without internet connectivity", void 0).option("-t, --timeout <timeout>", "Set API <timeout> in milliseconds to Coana backend.", "300000").option("-a, --analysis-timeout <timeout>", "Set <timeout> in seconds for each reachability analysis run").option("--memory-limit <memoryInMB>", "Set memory limit for analysis to <memoryInMB> megabytes of memory.", "8192").option("-c, --concurrency <concurrency>", "Set the maximum number of concurrent reachability analysis runs. It's recommended to choose a concurrency level that ensures that each analysis run has at least the --memory-limit amount of memory available.", "1").option("--api-key <key>", "Set the Coana dashboard API key. By setting you also enable the dashboard integration.").addOption(new Option("--write-report-to-file", "Write the report dashboard-compatible report to dashboard-report.json. This report may help the Coana team debug issues with the report insertion mechanism.").default(false).hideHelp()).option("--project-name <repoName>", "Set the name of the repository. Used for dashboard integration.").option("--repo-url <repoUrl>", "Set the URL of the repository. Used for dashboard integration.").option("--include-dirs <relativeDirs...>", "globs for directories to include from the detection of subprojects (space-separated)(use relative paths from the project root). Notice, projects that are not included may still be scanned if they are referenced from included projects.").option("--exclude-dirs <relativeDirs...>", "globs for directories to exclude from the detection of subprojects (space-separated)(use relative paths from the project root). Notice, excluded projects may still be scanned if they are referenced from non-excluded projects.").option("--disable-analysis-splitting", "Limits Coana to at most 1 reachability analysis run per workspace").option("--print-analysis-log-file", "Store log output from the JavaScript/TypeScript reachability analysis in the file js-analysis.log file in the root of each workspace", false).option("--entry-points <entryPoints...>", "List of files to analyze for root workspace. The reachability analysis automatically analyzes all files used by the entry points. If not provided, all JavaScript and TypeScript files are considered entry points. For non-root workspaces, all JavaScript and TypeScript files are analyzed as well.").option("--include-projects-with-no-reachability-support", "Also runs Coana on projects where we support traditional SCA, but does not yet support reachability analysis.", false).option("--ecosystems <ecosystems...>", "List of ecosystems to analyze (space-separated). Currently NPM, PIP, MAVEN, NUGET and GO are supported. Default is all supported ecosystems.").addOption(new Option("--purl-types <purlTypes...>", "List of PURL types to analyze (space-separated). Currently npm, pypi, maven, nuget, golang and cargo are supported. Default is all supported purl types.").hideHelp()).option("--changed-files <files...>", "List of files that have changed. If provided, Coana only analyzes workspaces and modules that contain changed files.").option("--disable-report-submission", "Disable the submission of the report to the Coana dashboard. Used by the pipeline blocking feature.", false).option("--disable-analytics-sharing", "Disable analytics sharing.", false).option("--provider-project <path>", "File system path to folder containing the provider project (Only supported for Maven, Gradle, and SBT)").option("--provider-workspaces <dirs...>", "List of workspaces that build the provided runtime environment (Only supported for Maven, Gradle, and SBT)", (paths) => paths.split(" ")).option("--lightweight-reachability", "Runs Coana in lightweight mode. This increases analysis speed but also raises the risk of Coana misclassifying the reachability of certain complex vulnerabilities. Recommended only for use with Coana Guardrail mode.", false).addOption(new Option("--run-without-docker", "Run package managers and reachability analyzers without using docker").default(process.env.RUN_WITHOUT_DOCKER === "true").hideHelp()).addOption(new Option("--run-env <env>", "Specifies the environment in which the CLI is run. So far only MANAGED_SCAN and UNKNOWN are supported.").default("UNKNOWN").choices(["UNKNOWN", "MANAGED_SCAN"]).hideHelp()).addOption(new Option("--guardrail-mode", "Run Coana in guardrail mode. This mode is used to prevent new reachable vulnerabilities from being introduced into the codebase. Usually run as a CI check when pushing new commits to a pull request.")).option("--ignore-failing-workspaces", "Continue processing when a workspace fails instead of exiting. Failed workspaces will be logged at termination.", false).addOption(new Option("--socket-mode <output-file>", "Run Coana in socket mode and write report to <output-file>").hideHelp()).addOption(new Option("--manifests-tar-hash <hash>", "Hash of the tarball containing all manifest files already uploaded to Socket. If provided, Socket will be used for computing dependency trees.").hideHelp()).option("--skip-cache-usage", "Do not attempt to use cached analysis configuration from previous runs", false).addOption(new Option("--min-severity <severity>", "Set the minimum severity of vulnerabilities to analyze. Supported severities are info, low, moderate, high and critical.").choices(["info", "INFO", "low", "LOW", "moderate", "MODERATE", "high", "HIGH", "critical", "CRITICAL"])).version(version2).configureHelp({ sortOptions: true }).action(async (path2, options) => {
246645
+ run2.name("run").argument("<path>", "File system path to folder containing the project").option("-o, --output-dir <path>", "Write json report to <path>/coana-report.json").option("-d, --debug", "Enable debug logging", false).option("-s, --silent", "Silence all debug/warning output", false).option("--silent-spinner", "Silence spinner", "CI" in process.env || !process.stdin.isTTY).option("-p, --print-report", "Print the report to the console", false).option("--offline-database <path>", "Path to a coana-offline-db.json file for running the CLI without internet connectivity", void 0).option("-t, --timeout <timeout>", "Set API <timeout> in milliseconds to Coana backend.", "300000").option("-a, --analysis-timeout <timeout>", "Set <timeout> in seconds for each reachability analysis run").option("--memory-limit <memoryInMB>", "Set memory limit for analysis to <memoryInMB> megabytes of memory.", "8192").option("-c, --concurrency <concurrency>", "Set the maximum number of concurrent reachability analysis runs. It's recommended to choose a concurrency level that ensures that each analysis run has at least the --memory-limit amount of memory available.", "1").option("--api-key <key>", "Set the Coana dashboard API key. By setting you also enable the dashboard integration.").addOption(new Option("--write-report-to-file", "Write the report dashboard-compatible report to dashboard-report.json. This report may help the Coana team debug issues with the report insertion mechanism.").default(false).hideHelp()).option("--project-name <repoName>", "Set the name of the repository. Used for dashboard integration.").option("--repo-url <repoUrl>", "Set the URL of the repository. Used for dashboard integration.").option("--include-dirs <relativeDirs...>", "globs for directories to include from the detection of subprojects (space-separated)(use relative paths from the project root). Notice, projects that are not included may still be scanned if they are referenced from included projects.").option("--exclude-dirs <relativeDirs...>", "globs for directories to exclude from the detection of subprojects (space-separated)(use relative paths from the project root). Notice, excluded projects may still be scanned if they are referenced from non-excluded projects.").option("--disable-analysis-splitting", "Limits Coana to at most 1 reachability analysis run per workspace").option("--print-analysis-log-file", "Store log output from the JavaScript/TypeScript reachability analysis in the file js-analysis.log file in the root of each workspace", false).option("--entry-points <entryPoints...>", "List of files to analyze for root workspace. The reachability analysis automatically analyzes all files used by the entry points. If not provided, all JavaScript and TypeScript files are considered entry points. For non-root workspaces, all JavaScript and TypeScript files are analyzed as well.").option("--include-projects-with-no-reachability-support", "Also runs Coana on projects where we support traditional SCA, but does not yet support reachability analysis.", false).option("--ecosystems <ecosystems...>", "List of ecosystems to analyze (space-separated). Currently NPM, PIP, MAVEN, NUGET and GO are supported. Default is all supported ecosystems.").addOption(new Option("--purl-types <purlTypes...>", "List of PURL types to analyze (space-separated). Currently npm, pypi, maven, nuget, golang and cargo are supported. Default is all supported purl types.").hideHelp()).option("--changed-files <files...>", "List of files that have changed. If provided, Coana only analyzes workspaces and modules that contain changed files.").option("--disable-report-submission", "Disable the submission of the report to the Coana dashboard. Used by the pipeline blocking feature.", false).option("--disable-analytics-sharing", "Disable analytics sharing.", false).option("--provider-project <path>", "File system path to folder containing the provider project (Only supported for Maven, Gradle, and SBT)").option("--provider-workspaces <dirs...>", "List of workspaces that build the provided runtime environment (Only supported for Maven, Gradle, and SBT)", (paths) => paths.split(" ")).option("--lightweight-reachability", "Runs Coana in lightweight mode. This increases analysis speed but also raises the risk of Coana misclassifying the reachability of certain complex vulnerabilities. Recommended only for use with Coana Guardrail mode.", false).addOption(new Option("--run-without-docker", "Run package managers and reachability analyzers without using docker").default(process.env.RUN_WITHOUT_DOCKER === "true").hideHelp()).addOption(new Option("--run-env <env>", "Specifies the environment in which the CLI is run. So far only MANAGED_SCAN and UNKNOWN are supported.").default("UNKNOWN").choices(["UNKNOWN", "MANAGED_SCAN"]).hideHelp()).addOption(new Option("--guardrail-mode", "Run Coana in guardrail mode. This mode is used to prevent new reachable vulnerabilities from being introduced into the codebase. Usually run as a CI check when pushing new commits to a pull request.")).option("--ignore-failing-workspaces", "Continue processing when a workspace fails instead of exiting. Failed workspaces will be logged at termination.", false).addOption(new Option("--socket-mode <output-file>", "Run Coana in socket mode and write report to <output-file>").hideHelp()).addOption(new Option("--manifests-tar-hash <hash>", "Hash of the tarball containing all manifest files already uploaded to Socket. If provided, Socket will be used for computing dependency trees.").hideHelp()).option("--skip-cache-usage", "Do not attempt to use cached analysis configuration from previous runs", false).addOption(new Option("--min-severity <severity>", "Set the minimum severity of vulnerabilities to analyze. Supported severities are info, low, moderate, high and critical.").choices(["info", "INFO", "low", "LOW", "moderate", "MODERATE", "high", "HIGH", "critical", "CRITICAL"])).option("--use-unreachable-from-precomputation", "Skip the reachability analysis for vulnerabilities that are already known to be unreachable from the precomputed reachability analysis (Tier 2).", false).version(version2).configureHelp({ sortOptions: true }).action(async (path2, options) => {
246598
246646
  process.env.DOCKER_IMAGE_TAG ??= version2;
246599
246647
  options.ecosystems = options.ecosystems?.map((e) => e.toUpperCase());
246600
246648
  options.minSeverity = options.minSeverity?.toUpperCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coana-tech/cli",
3
- "version": "14.12.71",
3
+ "version": "14.12.73",
4
4
  "description": "Coana CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -80792,6 +80792,9 @@ function hasReachableMatches(detectedOccurrences) {
80792
80792
  // ../web-compat-utils/src/vulnerability-reachability.ts
80793
80793
  function getVulnReachability(c) {
80794
80794
  if (c.type === "noAnalysisCheck") return "REACHABLE";
80795
+ if (c.type === "precomputed") {
80796
+ return c.results.type === "unreachable" ? "UNREACHABLE" : "UNKNOWN";
80797
+ }
80795
80798
  if (c.type !== "success") {
80796
80799
  return "UNKNOWN";
80797
80800
  }
@@ -109133,7 +109136,7 @@ async function setupDependenciesForAnalysis(subprojectDir, workspaceDir, directD
109133
109136
  });
109134
109137
  }
109135
109138
  function convertToPackageMetadatas(workspaceDir, dependenciesToInstall, directDependencies, artifactIdToArtifact) {
109136
- const artifactIdToDepedenciesToInstallIdx = Object.fromEntries(dependenciesToInstall.map((dep, idx) => [
109139
+ const artifactIdToDependenciesToInstallIdx = Object.fromEntries(dependenciesToInstall.map((dep, idx) => [
109137
109140
  Object.entries(artifactIdToArtifact).find(([_, artifact]) => artifact === dep.artifact)[0],
109138
109141
  idx + 1
109139
109142
  ]));
@@ -109146,16 +109149,16 @@ function convertToPackageMetadatas(workspaceDir, dependenciesToInstall, directDe
109146
109149
  installedPath: workspaceDir
109147
109150
  });
109148
109151
  for (const [parentId, parent2] of Object.entries(artifactIdToArtifact)) {
109149
- if (!(parentId in artifactIdToDepedenciesToInstallIdx))
109152
+ if (!(parentId in artifactIdToDependenciesToInstallIdx))
109150
109153
  continue;
109151
109154
  for (const child of parent2.dependencies ?? []) {
109152
109155
  const childArtifact = artifactIdToArtifact[child];
109153
- if (!artifactIdToDepedenciesToInstallIdx[child])
109156
+ if (!artifactIdToDependenciesToInstallIdx[child])
109154
109157
  continue;
109155
109158
  if (!artifactToParents.has(childArtifact)) {
109156
109159
  artifactToParents.set(childArtifact, []);
109157
109160
  }
109158
- artifactToParents.get(childArtifact).push(artifactIdToDepedenciesToInstallIdx[parentId]);
109161
+ artifactToParents.get(childArtifact).push(artifactIdToDependenciesToInstallIdx[parentId]);
109159
109162
  }
109160
109163
  }
109161
109164
  for (const directDependency of directDependencies) {
@@ -109262,9 +109265,9 @@ function computePackagePlacements(packageMetadatas, nodeModulesDir) {
109262
109265
  addPlacementToRelativeInstallDirsForSCC(sccToPlace, /* @__PURE__ */ new Set([nodeModulesDir]), installNames);
109263
109266
  return;
109264
109267
  }
109265
- const parentMetadatas = Array.from(curParents).map((parentIndex) => packageMetadataDag.sccs[parentIndex].packages).flat();
109268
+ const parentMetadatas = Array.from(curParents, (parentIndex) => packageMetadataDag.sccs[parentIndex].packages).flat();
109266
109269
  if (curSCC !== sccToPlace) {
109267
- const anyParentDependsOnDifferentPackageToPlace = parentMetadatas.some((parentPackage) => sccToPlace.packages.some((packageToPlace) => parentPackage.dependencies[packageToPlace.name] && // If the package to place has the parentPackage as a parent, then the version it depends on is actually the one to place, and therefore it should not have a different installation
109270
+ const anyParentDependsOnDifferentPackageToPlace = parentMetadatas.some((parentPackage) => sccToPlace.packages.some((packageToPlace) => Object.hasOwn(parentPackage.dependencies, packageToPlace.name) && // If the package to place has the parentPackage as a parent, then the version it depends on is actually the one to place, and therefore it should not have a different installation
109268
109271
  !packageToPlace.parents.some((p2p) => packageMetadatas[p2p] === parentPackage)));
109269
109272
  if (anyParentDependsOnDifferentPackageToPlace) {
109270
109273
  addPlacementToRelativeToPackages(sccToPlace, curSCC.packages, installNames);
@@ -109272,16 +109275,16 @@ function computePackagePlacements(packageMetadatas, nodeModulesDir) {
109272
109275
  }
109273
109276
  }
109274
109277
  const installNamesClone = new Map([...installNames.entries()].map(([packageName, installNames2]) => [packageName, new Set(installNames2)]));
109275
- const newInstallNames = parentMetadatas.flatMap((parentPackage) => sccToPlace.packages.flatMap((packageToPlace) => Object.entries(parentPackage.dependencies).filter(([name2, version3]) => name2 === packageToPlace.name || version3.startsWith("npm:") && version3.includes(`${packageToPlace.name}@`)).map(([name2, _version]) => [packageToPlace.name, name2])));
109276
- for (const [packageName, installName] of newInstallNames) {
109277
- if (!installNamesClone.has(packageName)) {
109278
- installNamesClone.set(packageName, /* @__PURE__ */ new Set());
109279
- }
109280
- installNamesClone.get(packageName).add(installName);
109278
+ const newInstallNames = parentMetadatas.flatMap((parentPackage) => sccToPlace.packages.flatMap((packageToPlace) => Object.entries(parentPackage.dependencies).filter(([name2, version3]) => name2 === packageToPlace.name || version3.startsWith("npm:") && version3.includes(`${packageToPlace.name}@`)).map(([name2]) => [packageToPlace, name2])));
109279
+ for (const [packageMetadata, installName] of newInstallNames) {
109280
+ const installNamesSet = installNamesClone.get(packageMetadata);
109281
+ if (!installNamesSet)
109282
+ installNamesClone.set(packageMetadata, /* @__PURE__ */ new Set([installName]));
109283
+ else
109284
+ installNamesSet.add(installName);
109281
109285
  }
109282
- for (const parent2 of curParents) {
109286
+ for (const parent2 of curParents)
109283
109287
  recHelper2(parent2, installNamesClone);
109284
- }
109285
109288
  };
109286
109289
  var recHelper = recHelper2;
109287
109290
  addPlacementsDirectIfMultipleVersions(sccToPlace);
@@ -109304,8 +109307,8 @@ function computePackagePlacements(packageMetadatas, nodeModulesDir) {
109304
109307
  const parentMetadata = packageMetadatas[parent2];
109305
109308
  const parentInstallDirs = computedPlacements.get(parentMetadata);
109306
109309
  if (parentInstallDirs) {
109307
- const dependencyInstallName = metadataToPlace.name in parentMetadata.dependencies ? metadataToPlace.name : Object.entries(parentMetadata.dependencies).find(([_name, version3]) => version3.startsWith("npm:") && version3.includes(`${metadataToPlace.name}@`))?.[0];
109308
- addPlacementToRelativeInstallDirs(metadataToPlace, parentInstallDirs, /* @__PURE__ */ new Map([[metadataToPlace.name, /* @__PURE__ */ new Set([dependencyInstallName ?? metadataToPlace.name])]]));
109310
+ const dependencyInstallName = Object.hasOwn(parentMetadata.dependencies, metadataToPlace.name) ? metadataToPlace.name : Object.entries(parentMetadata.dependencies).find(([, version3]) => version3.startsWith("npm:") && version3.includes(`${metadataToPlace.name}@`))?.[0];
109311
+ addPlacementToRelativeInstallDirs(metadataToPlace, parentInstallDirs, /* @__PURE__ */ new Map([[metadataToPlace, /* @__PURE__ */ new Set([dependencyInstallName ?? metadataToPlace.name])]]));
109309
109312
  }
109310
109313
  }
109311
109314
  }
@@ -109317,24 +109320,23 @@ function computePackagePlacements(packageMetadatas, nodeModulesDir) {
109317
109320
  }
109318
109321
  }
109319
109322
  function addPlacementToRelativeInstallDirsForSCC(sccToPlace, installDirs, installNames) {
109320
- for (const metadataToPlace of sccToPlace.packages) {
109323
+ for (const metadataToPlace of sccToPlace.packages)
109321
109324
  addPlacementToRelativeInstallDirs(metadataToPlace, installDirs, installNames);
109322
- }
109323
109325
  }
109324
109326
  function addPlacementToRelativeInstallDirs(metadataToPlace, installDirs, installNames) {
109325
109327
  if (metadataToPlace.name === ROOT_PACKAGE_METADATA_NAME) {
109326
109328
  return;
109327
109329
  }
109328
- if (!computedPlacements.has(metadataToPlace)) {
109329
- computedPlacements.set(metadataToPlace, /* @__PURE__ */ new Set());
109330
- }
109330
+ let computedPlacementsForMetadata = computedPlacements.get(metadataToPlace);
109331
+ if (!computedPlacementsForMetadata)
109332
+ computedPlacements.set(metadataToPlace, computedPlacementsForMetadata = /* @__PURE__ */ new Set());
109331
109333
  for (const installDir of [...installDirs]) {
109332
- for (const installName of installNames.get(metadataToPlace.name) ?? /* @__PURE__ */ new Set([metadataToPlace.name])) {
109334
+ for (const installName of installNames.get(metadataToPlace) ?? /* @__PURE__ */ new Set([metadataToPlace.name])) {
109333
109335
  if (installDir.endsWith(`/node_modules/${installName}`))
109334
109336
  continue;
109335
109337
  const computedInstallDir = installDir.endsWith(nodeModulesDir) ? resolve11(installDir, installName) : resolve11(installDir, "node_modules", installName);
109336
109338
  if (!allInstallDirs.has(computedInstallDir)) {
109337
- computedPlacements.get(metadataToPlace).add(computedInstallDir);
109339
+ computedPlacementsForMetadata.add(computedInstallDir);
109338
109340
  allInstallDirs.add(computedInstallDir);
109339
109341
  }
109340
109342
  }
@@ -112172,7 +112174,7 @@ async function analyzeWithHeuristics(state, vulns, heuristicsInOrder, doNotRecom
112172
112174
  try {
112173
112175
  newAnalysisRunListener();
112174
112176
  const initialBucketContainingAllVulns = buckets.length === 1 && buckets[0] === bucket;
112175
- logger.info(`Running analysis for ${vulnsForBucket.length}/${vulnerabilities.length} vulnerabilities.`);
112177
+ logger.info(`Running full reachability analysis for ${vulnsForBucket.length}/${vulnerabilities.length} vulnerabilities.`);
112176
112178
  const result = await codeAwareScanner.runAnalysis(vulnsForBucket, bucket.heuristic, initialBucketContainingAllVulns, experiment);
112177
112179
  const allowSplitInBuckets = !disableBucketing && bucket.heuristic.splitAnalysisInBuckets && !state.otherAnalysisOptions.disableBucketing && vulnDepIdentifiers.length > 1 && (result.type === "error" || result.reachedDependencies);
112178
112180
  if (result.type === "success") {
@@ -112462,6 +112464,18 @@ var import_picomatch4 = __toESM(require_picomatch4(), 1);
112462
112464
  import { existsSync as existsSync13 } from "fs";
112463
112465
  import { rm as rm6 } from "fs/promises";
112464
112466
  import { resolve as resolve20 } from "path";
112467
+
112468
+ // ../web-compat-utils/src/pluralize.ts
112469
+ function pluralize(count, word) {
112470
+ if (word === "vulnerability") {
112471
+ return count === 1 ? "vulnerability" : "vulnerabilities";
112472
+ } else {
112473
+ word;
112474
+ throw new Error(`Invalid word: ${word}`);
112475
+ }
112476
+ }
112477
+
112478
+ // dist/analyzers/npm-analyzer.js
112465
112479
  var { partition: partition3, memoize: memoize2 } = import_lodash19.default;
112466
112480
  var NpmAnalyzer = class {
112467
112481
  state;
@@ -112487,6 +112501,7 @@ var NpmAnalyzer = class {
112487
112501
  try {
112488
112502
  const vulnerabilityScanner = new JSCodeAwareVulnerabilityScanner(this.state.rootWorkingDir, this.projectDir, this.state.reachabilityAnalysisOptions);
112489
112503
  await vulnerabilityScanner.prepareDependencies(this.state, heuristicsInOrder[0]);
112504
+ logger.info(`Running import reachability analysis for ${vulns.length} ${pluralize(vulns.length, "vulnerability")}`);
112490
112505
  let reachable;
112491
112506
  try {
112492
112507
  statusUpdater?.("Running import reachability analysis");
@@ -112520,6 +112535,7 @@ var NpmAnalyzer = class {
112520
112535
  return /[*?{}]/.test(modGlob) ? checkGlob(modGlob) : !reachableModuleNames.has(modGlob);
112521
112536
  });
112522
112537
  }));
112538
+ logger.info(`Found ${unreachableVulns.length} ${pluralize(unreachableVulns.length, "vulnerability")} that ${unreachableVulns.length !== 1 ? "are" : "is"} unreachable from the import graph`);
112523
112539
  }
112524
112540
  const res = otherVulns.length ? await analyzeWithHeuristics(this.state, otherVulns, heuristicsInOrder, true, vulnerabilityScanner, analysisMetadataCollector, statusUpdater) : [];
112525
112541
  if (unreachableVulns.length) {