@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 +96 -48
- package/package.json +1 -1
- package/reachability-analyzers-cli.mjs +40 -24
- package/repos/coana-tech/alucard/alucard.jar +0 -0
- package/repos/coana-tech/goana/bin/goana-darwin-amd64.gz +0 -0
- package/repos/coana-tech/goana/bin/goana-darwin-arm64.gz +0 -0
- package/repos/coana-tech/goana/bin/goana-linux-amd64.gz +0 -0
- package/repos/coana-tech/goana/bin/goana-linux-arm64.gz +0 -0
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
|
|
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 ??
|
|
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(
|
|
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
|
-
|
|
230876
|
-
|
|
230877
|
-
|
|
230878
|
-
|
|
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
|
-
|
|
230959
|
-
|
|
230960
|
-
|
|
230961
|
-
|
|
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.
|
|
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 [
|
|
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
|
|
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:
|
|
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
|
@@ -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
|
|
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
|
|
109152
|
+
if (!(parentId in artifactIdToDependenciesToInstallIdx))
|
|
109150
109153
|
continue;
|
|
109151
109154
|
for (const child of parent2.dependencies ?? []) {
|
|
109152
109155
|
const childArtifact = artifactIdToArtifact[child];
|
|
109153
|
-
if (!
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
109276
|
-
for (const [
|
|
109277
|
-
|
|
109278
|
-
|
|
109279
|
-
|
|
109280
|
-
|
|
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 =
|
|
109308
|
-
addPlacementToRelativeInstallDirs(metadataToPlace, parentInstallDirs, /* @__PURE__ */ new Map([[metadataToPlace
|
|
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
|
-
|
|
109329
|
-
|
|
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
|
|
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
|
-
|
|
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) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|