@socketsecurity/cli-with-sentry 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +5 -5
  3. package/dist/cli.js +139 -147
  4. package/dist/cli.js.map +1 -1
  5. package/dist/constants.js +3 -3
  6. package/dist/constants.js.map +1 -1
  7. package/dist/manifest-scripts/dotnet-tool/socket-facts-dotnet.dll +0 -0
  8. package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
  9. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  10. package/dist/types/commands/fix/cmd-fix.d.mts.map +1 -1
  11. package/dist/types/commands/fix/coana-fix.d.mts.map +1 -1
  12. package/dist/types/commands/fix/generated-socket-facts.d.mts +4 -2
  13. package/dist/types/commands/fix/generated-socket-facts.d.mts.map +1 -1
  14. package/dist/types/commands/fix/types.d.mts +3 -1
  15. package/dist/types/commands/fix/types.d.mts.map +1 -1
  16. package/dist/types/commands/manifest/discover-manifest-roots.d.mts +3 -1
  17. package/dist/types/commands/manifest/discover-manifest-roots.d.mts.map +1 -1
  18. package/dist/types/commands/manifest/generate-recursive-manifests.d.mts +4 -2
  19. package/dist/types/commands/manifest/generate-recursive-manifests.d.mts.map +1 -1
  20. package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
  21. package/dist/types/commands/manifest/scripts/build-tool.d.mts +6 -0
  22. package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -1
  23. package/dist/types/commands/manifest/scripts/facts.d.mts +4 -7
  24. package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -1
  25. package/dist/types/commands/manifest/scripts/records.d.mts +0 -3
  26. package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -1
  27. package/dist/types/commands/manifest/scripts/resolution-report-nuget.d.mts +2 -2
  28. package/dist/types/commands/manifest/scripts/resolution-report-nuget.d.mts.map +1 -1
  29. package/dist/types/commands/manifest/scripts/run.d.mts +0 -3
  30. package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -1
  31. package/dist/types/commands/manifest/scripts/sidecar.d.mts +0 -4
  32. package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -1
  33. package/dist/types/commands/manifest/setup-recursive-manifest-config.d.mts.map +1 -1
  34. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  35. package/dist/types/commands/scan/cmd-scan-reach.d.mts.map +1 -1
  36. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  37. package/dist/types/commands/scan/handle-scan-reach.d.mts.map +1 -1
  38. package/dist/types/commands/scan/perform-reachability-analysis.d.mts +5 -1
  39. package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
  40. package/dist/types/commands/scan/reachability-flags.d.mts +6 -0
  41. package/dist/types/commands/scan/reachability-flags.d.mts.map +1 -1
  42. package/dist/types/commands/scan/run-dynamic-sbom-inference.d.mts +16 -9
  43. package/dist/types/commands/scan/run-dynamic-sbom-inference.d.mts.map +1 -1
  44. package/dist/types/utils/coana.d.mts +6 -0
  45. package/dist/types/utils/coana.d.mts.map +1 -1
  46. package/dist/types/utils/ecosystem.d.mts +1 -0
  47. package/dist/types/utils/ecosystem.d.mts.map +1 -1
  48. package/dist/utils.js +20 -7
  49. package/dist/utils.js.map +1 -1
  50. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1914,23 +1914,11 @@ function serializeSidecar(acc) {
1914
1914
  return result;
1915
1915
  }
1916
1916
 
1917
- // Tells Coana which ecosystems' artifacts only these facts files attribute.
1918
- // Maven's always are (the dynamic-SBOM-inference contract), under its own flag
1919
- // every Coana release knows unless another ecosystem's facts are present too.
1920
- function useOnlySocketFactsArtifactsFlags(sidecar) {
1921
- const types = new Set(['maven']);
1922
- for (const entry of Object.values(sidecar ?? {})) {
1923
- for (const item of [...entry.projects, ...entry.components]) {
1924
- types.add(item.type);
1925
- }
1926
- }
1927
- return types.size === 1 ? ['--maven-use-only-socket-facts'] : ['--use-only-socket-facts-artifacts', [...types].sort().join(',')];
1928
- }
1929
-
1930
1917
  async function performReachabilityAnalysis(options) {
1931
1918
  const {
1932
1919
  branchName,
1933
1920
  cwd = process.cwd(),
1921
+ factsOnlyTypes,
1934
1922
  orgSlug,
1935
1923
  outputKind = 'text',
1936
1924
  outputPath,
@@ -2023,21 +2011,18 @@ async function performReachabilityAnalysis(options) {
2023
2011
  spinner?.infoAndStop('Running reachability analysis with Coana...');
2024
2012
 
2025
2013
  // Temp file for --compute-artifacts-sidecar, removed in the finally below.
2026
- // Written even when empty under dynamicSbomInference, since the
2027
- // facts-only flag below requires one to be present.
2014
+ // Written even when empty with factsOnlyTypes: `coana run` rejects the
2015
+ // facts-only flag without one.
2028
2016
  let sidecarPath;
2029
- if (reachabilityOptions.dynamicSbomInference || resolvedPathsSidecar && hasResolvedPathsSidecarEntries(resolvedPathsSidecar)) {
2017
+ if (factsOnlyTypes || resolvedPathsSidecar && hasResolvedPathsSidecarEntries(resolvedPathsSidecar)) {
2030
2018
  sidecarPath = path.join(os.tmpdir(), `socket-compute-artifacts-sidecar-${require$$0.randomUUID()}.json`);
2031
- await u.promises.writeFile(sidecarPath, JSON.stringify(resolvedPathsSidecar ?? {}), 'utf8');
2019
+ await u.promises.writeFile(sidecarPath, JSON.stringify(resolvedPathsSidecar ?? {}));
2032
2020
  }
2033
2021
 
2034
2022
  // Build Coana arguments.
2035
2023
  const coanaArgs = ['run', analysisTarget, '--output-dir', path.dirname(outputFilePath), '--socket-mode', outputFilePath, '--disable-report-submission', ...(isOmittedReachValue(reachabilityOptions.reachAnalysisTimeout) ? [] : ['--analysis-timeout', reachabilityOptions.reachAnalysisTimeout]), ...(isOmittedReachValue(reachabilityOptions.reachAnalysisMemoryLimit) ? [] : ['--memory-limit', reachabilityOptions.reachAnalysisMemoryLimit]), ...(reachabilityOptions.reachConcurrency ? ['--concurrency', `${reachabilityOptions.reachConcurrency}`] : []), ...(reachabilityOptions.reachContinueOnAnalysisErrors ? ['--reach-continue-on-analysis-errors'] : []), ...(reachabilityOptions.reachContinueOnInstallErrors ? ['--reach-continue-on-install-errors'] : []), ...(reachabilityOptions.reachContinueOnMissingLockFiles ? ['--reach-continue-on-missing-lock-files'] : []), ...(reachabilityOptions.reachContinueOnNoSourceFiles ? ['--reach-continue-on-no-source-files'] : []), ...(reachabilityOptions.reachDebug ? ['--debug'] : []), ...(reachabilityOptions.reachDetailedAnalysisLogFile ? ['--print-analysis-log-file'] : []), ...(reachabilityOptions.reachDisableAnalytics ? ['--disable-analytics-sharing'] : []), ...(reachabilityOptions.reachDisableDependencyDownloads ? ['--disable-dependency-downloads'] : []), ...(reachabilityOptions.reachDisableExternalToolChecks ? ['--disable-external-tool-checks'] : []), ...(reachabilityOptions.reachEnableAnalysisSplitting ? [] : ['--disable-analysis-splitting']), '--run-without-docker', '--manifests-tar-hash', tarHash,
2036
2024
  // Empty reachEcosystems implies scanning all ecosystems.
2037
- ...(reachabilityOptions.reachEcosystems.length ? ['--purl-types', ...reachabilityOptions.reachEcosystems] : []), ...(reachabilityOptions.reachAdvisories.length ? ['--ghsas', ...reachabilityOptions.reachAdvisories] : []), ...(reachabilityOptions.reachExcludePaths.length ? ['--exclude-dirs', ...reachabilityOptions.reachExcludePaths] : []),
2038
- // sidecarPath is always set above when this is true - Coana rejects this
2039
- // flag without one.
2040
- ...(reachabilityOptions.dynamicSbomInference ? useOnlySocketFactsArtifactsFlags(resolvedPathsSidecar) : []), ...(reachabilityOptions.reachSkipCache ? ['--skip-cache-usage'] : []), ...(reachabilityOptions.reachUseOnlyPregeneratedSboms ? ['--use-only-pregenerated-sboms'] : []), ...(sidecarPath ? ['--compute-artifacts-sidecar', sidecarPath] : [])];
2025
+ ...(reachabilityOptions.reachEcosystems.length ? ['--purl-types', ...reachabilityOptions.reachEcosystems] : []), ...(reachabilityOptions.reachAdvisories.length ? ['--ghsas', ...reachabilityOptions.reachAdvisories] : []), ...(reachabilityOptions.reachExcludePaths.length ? ['--exclude-dirs', ...reachabilityOptions.reachExcludePaths] : []), ...(factsOnlyTypes ? utils.useOnlySocketFactsArtifactsFlags(factsOnlyTypes) : []), ...(reachabilityOptions.reachSkipCache ? ['--skip-cache-usage'] : []), ...(reachabilityOptions.reachUseOnlyPregeneratedSboms ? ['--use-only-pregenerated-sboms'] : []), ...(sidecarPath ? ['--compute-artifacts-sidecar', sidecarPath] : [])];
2041
2026
 
2042
2027
  // Build environment variables.
2043
2028
  const coanaEnv = {};
@@ -2114,6 +2099,21 @@ const PURL_TYPE_BY_TOOL = {
2114
2099
  maven: 'maven',
2115
2100
  sbt: 'maven'
2116
2101
  };
2102
+ const BUILD_TOOLS = Object.keys(PURL_TYPE_BY_TOOL);
2103
+ const ECOSYSTEM_LABELS = {
2104
+ __proto__: null,
2105
+ dotnet: '.NET',
2106
+ gradle: 'Gradle',
2107
+ maven: 'Maven',
2108
+ sbt: 'sbt'
2109
+ };
2110
+ const FACTS_PURL_TYPES = [...new Set(Object.values(PURL_TYPE_BY_TOOL))].sort();
2111
+ function buildToolsFor(purlTypes) {
2112
+ return BUILD_TOOLS.filter(tool => purlTypes.includes(PURL_TYPE_BY_TOOL[tool]));
2113
+ }
2114
+ function buildToolLabelsFor(purlTypes) {
2115
+ return buildToolsFor(purlTypes).map(tool => ECOSYSTEM_LABELS[tool]);
2116
+ }
2117
2117
 
2118
2118
  // Purl types whose packages have no namespace (NuGet ids are flat).
2119
2119
  const NAMESPACELESS_PURL_TYPES = new Set(['nuget']);
@@ -2644,8 +2644,6 @@ function buildFrameworks(parsed, perRoot, exists) {
2644
2644
  })),
2645
2645
  // In compiler order, which F# depends on.
2646
2646
  sources: [...new Set(build?.sources)].filter(exists),
2647
- references: [...new Set(build?.references)].filter(exists),
2648
- analyzers: [...new Set(build?.analyzers)].filter(exists),
2649
2647
  targets: sortedUnique(build?.targets ?? []).filter(exists),
2650
2648
  dependencies: [],
2651
2649
  classpath: []
@@ -2940,15 +2938,12 @@ function serializeExcludePathPatterns(paths) {
2940
2938
  // projectInfo names, as the dotnet tool's Reader.cs writes them.
2941
2939
  const PROJECT_INFO_LISTS = {
2942
2940
  __proto__: null,
2943
- Analyzer: 'analyzers',
2944
2941
  DefineConstant: 'defineConstants',
2945
- FrameworkReference: 'frameworkReferences',
2946
- Reference: 'references'
2942
+ FrameworkReference: 'frameworkReferences'
2947
2943
  };
2948
2944
  const PROJECT_INFO_VALUES = {
2949
2945
  __proto__: null,
2950
2946
  AssemblyName: 'assemblyName',
2951
- BuildError: 'buildError',
2952
2947
  Framework: 'framework',
2953
2948
  LangVersion: 'langVersion',
2954
2949
  Language: 'language',
@@ -3028,10 +3023,8 @@ function parseRecords(text) {
3028
3023
  let b = builds.get(config);
3029
3024
  if (!b) {
3030
3025
  b = {
3031
- analyzers: [],
3032
3026
  defineConstants: [],
3033
3027
  frameworkReferences: [],
3034
- references: [],
3035
3028
  sources: [],
3036
3029
  targets: [],
3037
3030
  usings: []
@@ -3512,25 +3505,6 @@ async function readDotnetSdkVersion(bin, opts) {
3512
3505
  }
3513
3506
  return out.stdout.trim();
3514
3507
  }
3515
- const noSdkWarnings = new Set();
3516
-
3517
- // True, after a warning, when no .NET SDK runs in `dir` (none installed, or
3518
- // none matching its global.json), so its .NET builds are skipped.
3519
- async function skipWithoutDotnetSdk(bin, dir) {
3520
- try {
3521
- await dotnetSdkVersion(bin, {
3522
- projectDir: dir
3523
- });
3524
- return false;
3525
- } catch (e) {
3526
- const reason = utils.getErrorMessageOr(e, `\`${bin} --version\` failed`).split('\n')[0];
3527
- if (!noSdkWarnings.has(reason)) {
3528
- noSdkWarnings.add(reason);
3529
- logger.logger.warn(`Skipping .NET builds where no .NET SDK runs (${reason}); their project files are scanned as they are.`);
3530
- }
3531
- return true;
3532
- }
3533
- }
3534
3508
 
3535
3509
  // Runs the bundled tool against the build's own dotnet, in `mode` (none: a
3536
3510
  // full facts run), and returns its exit and records.
@@ -3601,7 +3575,6 @@ async function readDotnetEntryProjects(opts) {
3601
3575
  return parsed.members.map(m => path.resolve(opts.projectDir, m));
3602
3576
  }
3603
3577
 
3604
- const BUILD_TOOLS = ['dotnet', 'gradle', 'maven', 'sbt'];
3605
3578
  const BUILD_FILE_MATCHERS = new Map(BUILD_TOOLS.map(tool => {
3606
3579
  const files = BUILD_FILES[tool];
3607
3580
  return [tool, files.byFile ? vendor.micromatchExports.matcher(files.nocaseGlob, {
@@ -3748,10 +3721,11 @@ function sortByDepthThenPath(roots, cwd, tool) {
3748
3721
  async function findBuildToolCandidates({
3749
3722
  cwd,
3750
3723
  excludePaths,
3724
+ purlTypes = FACTS_PURL_TYPES,
3751
3725
  sockJson,
3752
3726
  socketConfig
3753
3727
  }) {
3754
- const enabledTools = BUILD_TOOLS.filter(tool => !sockJson.defaults?.manifest?.[tool]?.disabled);
3728
+ const enabledTools = buildToolsFor(purlTypes).filter(tool => !sockJson.defaults?.manifest?.[tool]?.disabled);
3755
3729
  const result = new Map();
3756
3730
  if (!enabledTools.length) {
3757
3731
  return result;
@@ -4072,8 +4046,8 @@ const NOT_FOUND_CODES = new Set(['NU1100', 'NU1101', 'NU1102', 'NU1103']);
4072
4046
  const SOURCE_CODES = new Set(['NU1301', 'NU1302']);
4073
4047
 
4074
4048
  // The socket-facts-dotnet tool reports restore errors as `<code>: <message>`,
4075
- // and a design-time build failure as `<finding>: <code>: <message>`; its
4076
- // findings without a code count as other failures.
4049
+ // and evaluation errors as `<finding>: <code>: <message>`; its findings
4050
+ // without a code count as other failures.
4077
4051
  function classifyNugetFailure(detail) {
4078
4052
  const code = /(?:^|: )(NU\d{4}|NETSDK\d{4}|MSB\d{4}):/.exec(detail || '')?.[1];
4079
4053
  if (!code) {
@@ -4734,16 +4708,6 @@ async function runEcosystemCandidates({
4734
4708
  });
4735
4709
  continue;
4736
4710
  }
4737
- if (ecosystem === 'dotnet' && (
4738
- // eslint-disable-next-line no-await-in-loop
4739
- await skipWithoutDotnetSdk(dirConfig.bin, dir))) {
4740
- outcomes.push({
4741
- ...root,
4742
- ecosystem,
4743
- status: 'skippedNoSdk'
4744
- });
4745
- continue;
4746
- }
4747
4711
  // eslint-disable-next-line no-await-in-loop
4748
4712
  const result = await buildUncoveredRoot({
4749
4713
  config: {
@@ -4873,6 +4837,7 @@ async function generateRecursiveManifests({
4873
4837
  excludePaths,
4874
4838
  ignoreUnresolved,
4875
4839
  optionFlags = new Set(),
4840
+ purlTypes,
4876
4841
  sbtTmpDir,
4877
4842
  sidecarAcc,
4878
4843
  verbose,
@@ -4889,6 +4854,7 @@ async function generateRecursiveManifests({
4889
4854
  const candidatesByTool = await findBuildToolCandidates({
4890
4855
  cwd,
4891
4856
  excludePaths,
4857
+ purlTypes,
4892
4858
  socketConfig: utils.loadSocketConfig(cwd),
4893
4859
  sockJson: withoutDisabledFlags(rootSockJson)
4894
4860
  });
@@ -4978,38 +4944,64 @@ function resolveEcosystemConfig(ecosystem, dir, sockJson) {
4978
4944
  };
4979
4945
  }
4980
4946
 
4947
+ // Who consumes the facts: Coana for `fix` and `reach` (which also resolves
4948
+ // artifact paths), or only the scan upload.
4949
+
4950
+ // Coana takes the discovered ecosystems' artifacts only from facts files, so a
4951
+ // build root without them is one it leaves out.
4952
+ function checkFactsForCoana(outcomes, consumer, hasFacts) {
4953
+ const hasDisabled = outcomes.some(o => o.status === 'skippedDisabled');
4954
+ // Coana (15.14.1) refuses a facts-only reachability run without any facts.
4955
+ if (consumer === 'reach' && !hasFacts) {
4956
+ throw new utils.InputError(['No build root produced Socket facts, which reachability analysis with --dynamic-sbom-inference needs.', '', ...(hasDisabled ? [`- Re-enable the builds that socket.json skips (\`${DSI_SETUP_COMMAND}\` walks you through it).`] : []), '- Or select other ecosystems with --dynamic-sbom-inference-ecosystems.', '- Or remove --dynamic-sbom-inference to analyze the manifest files as they are.'].join('\n'));
4957
+ }
4958
+ if (hasDisabled) {
4959
+ logger.logger.warn('The analysis leaves out the build roots socket.json skips: with --dynamic-sbom-inference, it takes their dependencies only from Socket facts.');
4960
+ }
4961
+ }
4962
+
4981
4963
  // Recursively discovers and generates Socket facts for every independent
4982
- // gradle/sbt/maven build root under `cwd`, returning the generated facts
4983
- // paths plus the resolved-paths sidecar that reachability forwards to Coana.
4964
+ // build root of the selected ecosystems under `cwd`, returning the generated
4965
+ // facts paths plus the resolved-paths sidecar that reachability forwards to
4966
+ // Coana.
4984
4967
  async function runDynamicSbomInference({
4968
+ consumer,
4985
4969
  cwd,
4986
4970
  excludePaths,
4987
- sbtTmpDir,
4988
- sidecar,
4989
- withFiles
4971
+ purlTypes,
4972
+ sbtTmpDir
4990
4973
  }) {
4991
- const sidecarAcc = sidecar ?? withFiles ? new Map() : undefined;
4974
+ const withFiles = consumer === 'reach';
4975
+ const coanaReadsFacts = consumer === 'fix' || consumer === 'reach';
4976
+ const sidecarAcc = coanaReadsFacts ? new Map() : undefined;
4992
4977
  const outcomes = await generateRecursiveManifests({
4993
4978
  cwd,
4994
4979
  excludePaths,
4980
+ purlTypes,
4995
4981
  sbtTmpDir: withFiles ? sbtTmpDir : undefined,
4996
4982
  sidecarAcc,
4997
4983
  verbose: false,
4998
4984
  withFiles
4999
4985
  });
4986
+ const factsOnlyTypes = [...new Set(outcomes.map(o => PURL_TYPE_BY_TOOL[o.ecosystem]))];
5000
4987
  // No candidates discovered at all (distinct from candidates that were found
5001
4988
  // but produced no generated facts - empty/skippedDisabled are already warned
5002
4989
  // about elsewhere and are not this kind of mistake).
5003
- if (!outcomes.length) {
5004
- throw new utils.InputError(['No Gradle, sbt, Maven or .NET build root was found.', '', '- Remove --dynamic-sbom-inference; it only applies to these ecosystems.', '- Make sure to run it from the correct dir (use --cwd to target another dir).'].join('\n'));
4990
+ if (!utils.isFactsOnlyTypes(factsOnlyTypes)) {
4991
+ throw new utils.InputError([`No ${arrays.joinOr(buildToolLabelsFor(purlTypes))} build root was found.`, '', '- Select other ecosystems with --dynamic-sbom-inference-ecosystems, or remove --dynamic-sbom-inference.', '- Make sure to run it from the correct dir (use --cwd to target another dir).'].join('\n'));
5005
4992
  }
5006
4993
  // Fail loud rather than silently proceed with a partial multi-root result:
5007
4994
  // matches handleManifestDynamicSbomInference's own check.
5008
4995
  if (outcomes.some(o => o.status === 'failed')) {
5009
4996
  throw new utils.InputError('One or more independent build roots failed to generate Socket facts; aborting (see the errors above).');
5010
4997
  }
4998
+ const factsPaths = outcomes.filter(o => o.status === 'generated').map(o => o.factsPath);
4999
+ if (coanaReadsFacts) {
5000
+ checkFactsForCoana(outcomes, consumer, !!factsPaths.length);
5001
+ }
5011
5002
  return {
5012
- factsPaths: outcomes.filter(o => o.status === 'generated').map(o => o.factsPath),
5003
+ factsOnlyTypes,
5004
+ factsPaths,
5013
5005
  resolvedPathsSidecar: sidecarAcc && hasSidecarEntries(sidecarAcc) ? serializeSidecar(sidecarAcc) : undefined
5014
5006
  };
5015
5007
  }
@@ -8516,19 +8508,16 @@ async function generateAutoManifest({
8516
8508
  abortFactsRunIfFailed('maven', outcome);
8517
8509
  }
8518
8510
  if (!sockJson?.defaults?.manifest?.dotnet?.disabled && detected.dotnet) {
8519
- const config = resolveEcosystemConfig('dotnet', cwd, sockJson);
8520
- if (!(await skipWithoutDotnetSdk(config.bin, cwd))) {
8521
- logger.logger.log('Detected a .NET solution or project, generating Socket facts...');
8522
- const outcome = await convertDotnetToFacts({
8523
- config,
8524
- cwd,
8525
- excludePaths,
8526
- hints,
8527
- sidecarAcc,
8528
- withFiles: computeArtifactsSidecar
8529
- });
8530
- abortFactsRunIfFailed('dotnet', outcome);
8531
- }
8511
+ logger.logger.log('Detected a .NET solution or project, generating Socket facts...');
8512
+ const outcome = await convertDotnetToFacts({
8513
+ config: resolveEcosystemConfig('dotnet', cwd, sockJson),
8514
+ cwd,
8515
+ excludePaths,
8516
+ hints,
8517
+ sidecarAcc,
8518
+ withFiles: computeArtifactsSidecar
8519
+ });
8520
+ abortFactsRunIfFailed('dotnet', outcome);
8532
8521
  }
8533
8522
  if (!sockJson?.defaults?.manifest?.conda?.disabled && detected.conda) {
8534
8523
  const condaFile = sockJson.defaults?.manifest?.conda?.infile || detected.condaFile || constants.ENVIRONMENT_YML;
@@ -8653,9 +8642,10 @@ async function createNewScan({
8653
8642
 
8654
8643
  // Sidecar forwarded to reachability; populated only when reach runs.
8655
8644
  let resolvedPathsSidecar;
8645
+ let factsOnlyTypes;
8656
8646
 
8657
8647
  // sbt provisions its Scala toolchain under the directory passed as its
8658
- // isolated global base; withFiles' artifactPaths point into it, so it must
8648
+ // isolated global base; reach's artifactPaths point into it, so it must
8659
8649
  // stay on disk until the reachability analysis below has consumed those
8660
8650
  // paths. Always allocated (even when unused) to keep this uniform rather
8661
8651
  // than conditional on autoManifest/reach.
@@ -8669,18 +8659,20 @@ async function createNewScan({
8669
8659
  // one at cwd. This runs on its own, so nothing outside those
8670
8660
  // ecosystems is generated unless --auto-manifest also asked for it.
8671
8661
  const dynamicResult = await runDynamicSbomInference({
8662
+ consumer: reach.runReachabilityAnalysis ? 'reach' : 'upload',
8672
8663
  cwd,
8673
8664
  excludePaths: reach.excludePaths,
8665
+ purlTypes: reach.dynamicSbomInference,
8674
8666
  // sbt's Scala toolchain lives under its shared global base;
8675
- // withFiles' resolved paths point into it, so when reachability
8667
+ // reach's artifactPaths point into it, so when reachability
8676
8668
  // will consume them afterward, reuse manifestTmpDir (kept alive
8677
8669
  // until reach finishes below) instead of letting the call clean
8678
8670
  // its own ephemeral base up before reach ever reads those paths.
8679
- sbtTmpDir: manifestTmpDir,
8680
- withFiles: reach.runReachabilityAnalysis
8671
+ sbtTmpDir: manifestTmpDir
8681
8672
  });
8682
8673
  scanTargets = Array.from(new Set([...scanTargets, ...dynamicResult.factsPaths]));
8683
8674
  resolvedPathsSidecar = dynamicResult.resolvedPathsSidecar;
8675
+ factsOnlyTypes = dynamicResult.factsOnlyTypes;
8684
8676
  }
8685
8677
  if (autoManifest) {
8686
8678
  const sockJson = utils.readOrDefaultSocketJson(cwd);
@@ -8691,10 +8683,9 @@ async function createNewScan({
8691
8683
  if (reach.dynamicSbomInference) {
8692
8684
  // Already generated recursively above; resolving cwd's own build
8693
8685
  // root a second time would race on the same facts file.
8694
- detected.dotnet = false;
8695
- detected.gradle = false;
8696
- detected.sbt = false;
8697
- detected.maven = false;
8686
+ for (const tool of buildToolsFor(reach.dynamicSbomInference)) {
8687
+ detected[tool] = false;
8688
+ }
8698
8689
  }
8699
8690
  const autoManifestResult = await generateAutoManifest({
8700
8691
  computeArtifactsSidecar: reach.runReachabilityAnalysis,
@@ -8794,6 +8785,7 @@ async function createNewScan({
8794
8785
  const reachResult = await performReachabilityAnalysis({
8795
8786
  branchName,
8796
8787
  cwd,
8788
+ factsOnlyTypes,
8797
8789
  orgSlug,
8798
8790
  outputKind,
8799
8791
  packagePaths,
@@ -8980,7 +8972,7 @@ async function handleCi(autoManifest) {
8980
8972
  pendingHead: true,
8981
8973
  pullRequest: detectCiPullRequestNumber(),
8982
8974
  reach: {
8983
- dynamicSbomInference: false,
8975
+ dynamicSbomInference: undefined,
8984
8976
  excludePaths: [],
8985
8977
  reachAdvisories: [],
8986
8978
  reachAnalysisMemoryLimit: '',
@@ -10342,6 +10334,7 @@ async function generateSocketFactsForFix({
10342
10334
  cwd,
10343
10335
  excludePaths,
10344
10336
  findFactsPaths,
10337
+ purlTypes,
10345
10338
  tmpDir
10346
10339
  }) {
10347
10340
  const existingPaths = await findFactsPaths();
@@ -10355,24 +10348,24 @@ async function generateSocketFactsForFix({
10355
10348
  };
10356
10349
  let artifactsFlags;
10357
10350
  let paths;
10358
- let sidecarFile;
10351
+ const sidecarFile = path.join(tmpDir, 'sidecar.json');
10359
10352
  try {
10360
10353
  const {
10354
+ factsOnlyTypes,
10361
10355
  factsPaths,
10362
10356
  resolvedPathsSidecar
10363
10357
  } = await runDynamicSbomInference({
10358
+ consumer: 'fix',
10364
10359
  cwd,
10365
10360
  excludePaths,
10366
- sbtTmpDir: undefined,
10367
- sidecar: true,
10368
- withFiles: false
10361
+ purlTypes,
10362
+ sbtTmpDir: undefined
10369
10363
  });
10370
- artifactsFlags = useOnlySocketFactsArtifactsFlags(resolvedPathsSidecar);
10364
+ artifactsFlags = utils.useOnlySocketFactsArtifactsFlags(factsOnlyTypes);
10371
10365
  paths = factsPaths.map(p => path.resolve(cwd, p));
10372
- if (resolvedPathsSidecar) {
10373
- sidecarFile = path.join(tmpDir, 'sidecar.json');
10374
- await fs.writeFile(sidecarFile, JSON.stringify(resolvedPathsSidecar));
10375
- }
10366
+ // Written even when empty, as for reach; applying fixes looks each facts
10367
+ // file up in it, so an empty one is the same as none.
10368
+ await fs.writeFile(sidecarFile, JSON.stringify(resolvedPathsSidecar ?? {}));
10376
10369
  } catch (e) {
10377
10370
  // A failed build root aborts inference after others wrote their facts.
10378
10371
  await reset(await findFactsPaths());
@@ -10522,11 +10515,13 @@ function formatNotAppliedFixes(notApplied) {
10522
10515
  return notApplied.map(f => ` ${f.ghsa}: ${f.purl} -> ${f.fixedVersion}: ${f.reason}`).join('\n');
10523
10516
  }
10524
10517
  async function coanaFix(fixConfig) {
10525
- if (!fixConfig.dynamicSbomInference) {
10518
+ const purlTypes = fixConfig.dynamicSbomInference;
10519
+ if (!purlTypes) {
10526
10520
  return await coanaFixWithFacts(fixConfig, undefined);
10527
10521
  }
10528
10522
  return await utils.withTmpDir('socket-fix-facts-', async tmpDir => {
10529
10523
  const slot = {
10524
+ purlTypes,
10530
10525
  tmpDir
10531
10526
  };
10532
10527
  try {
@@ -10610,11 +10605,12 @@ async function coanaFixWithFacts(fixConfig, factsSlot) {
10610
10605
  if (factsSlot) {
10611
10606
  if (!silence) {
10612
10607
  spinner?.stop();
10613
- logger.logger.info('Generating Socket facts for .NET, Gradle, sbt and Maven builds ...');
10608
+ logger.logger.info('Generating Socket facts for each build ...');
10614
10609
  }
10615
10610
  factsSlot.generated = await generateSocketFactsForFix({
10616
10611
  cwd,
10617
10612
  excludePaths,
10613
+ purlTypes: factsSlot.purlTypes,
10618
10614
  findFactsPaths: async () => (await findScanFilepaths()).filter(utils.isSocketFactsFile),
10619
10615
  tmpDir: factsSlot.tmpDir
10620
10616
  });
@@ -11247,6 +11243,26 @@ async function handleFix({
11247
11243
  // differs: reachability is optional on `scan create` and always on for
11248
11244
  // `scan reach`.
11249
11245
  const DYNAMIC_SBOM_INFERENCE_DESCRIPTION = 'For Gradle, sbt, Maven and .NET: generate a Socket facts SBOM (produced directly by each package manager) per independent build root, instead of one synthetic root.';
11246
+
11247
+ // Not in reachabilityFlags: it applies to a `scan create` without --reach too.
11248
+ const dynamicSbomInferenceEcosystemsFlags = {
11249
+ dynamicSbomInferenceEcosystems: {
11250
+ type: 'string',
11251
+ isMultiple: true,
11252
+ description: `With --dynamic-sbom-inference: generate Socket facts only for these ecosystems, as either a comma separated value or as multiple flags; the others keep their manifest files as they are. Supported: ${arrays.joinAnd(FACTS_PURL_TYPES.map(t => `${t} (${arrays.joinAnd(buildToolLabelsFor([t]))})`))}. Defaults to all of them.`
11253
+ }
11254
+ };
11255
+ function parseDynamicSbomInferenceFlags(flags) {
11256
+ const raw = utils.cmdFlagValueToArray(flags['dynamicSbomInferenceEcosystems']);
11257
+ if (!flags['dynamicSbomInference']) {
11258
+ if (raw.length) {
11259
+ throw new utils.InputError('--dynamic-sbom-inference-ecosystems requires --dynamic-sbom-inference');
11260
+ }
11261
+ return undefined;
11262
+ }
11263
+ const selected = utils.parseEcosystemChoices(raw, FACTS_PURL_TYPES);
11264
+ return utils.isFactsOnlyTypes(selected) ? selected : FACTS_PURL_TYPES;
11265
+ }
11250
11266
  const reachabilityFlags = {
11251
11267
  reachVersion: {
11252
11268
  type: 'string',
@@ -11495,6 +11511,7 @@ Available styles:
11495
11511
  default: false,
11496
11512
  description: `${DYNAMIC_SBOM_INFERENCE_DESCRIPTION} Fixes are then attributed to the projects/modules that actually resolve each vulnerable dependency. The generated files are removed afterwards.`
11497
11513
  },
11514
+ ...dynamicSbomInferenceEcosystemsFlags,
11498
11515
  ecosystems: {
11499
11516
  type: 'string',
11500
11517
  default: [],
@@ -11629,7 +11646,6 @@ async function run$O(argv, importMeta, {
11629
11646
  autopilot,
11630
11647
  debug,
11631
11648
  disableExternalToolChecks,
11632
- dynamicSbomInference,
11633
11649
  ecosystems,
11634
11650
  exclude,
11635
11651
  excludePaths,
@@ -11655,23 +11671,10 @@ async function run$O(argv, importMeta, {
11655
11671
  const minSatisfying = cli.flags['minSatisfying'] || !maxSatisfying;
11656
11672
  const disableMajorUpdates = !majorUpdates;
11657
11673
  const outputKind = utils.getOutputKind(json, markdown);
11658
-
11659
- // Process comma-separated values for ecosystems flag.
11660
- // ALL_ECOSYSTEMS is lowercase, so normalize input for a case-insensitive
11661
- // match (mirrors --package-managers behavior).
11662
- const ecosystemsRaw = utils.cmdFlagValueToArray(ecosystems).map(s => s.toLowerCase());
11674
+ const dynamicSbomInference = parseDynamicSbomInferenceFlags(cli.flags);
11663
11675
 
11664
11676
  // Validate ecosystem values early, before dry-run check.
11665
- const validatedEcosystems = [];
11666
- const validEcosystemChoices = utils.getEcosystemChoicesForMeow();
11667
- for (const ecosystem of ecosystemsRaw) {
11668
- if (!validEcosystemChoices.includes(ecosystem)) {
11669
- logger.logger.fail(`Invalid ecosystem: "${ecosystem}". Valid values are: ${arrays.joinAnd(validEcosystemChoices)}`);
11670
- process.exitCode = 1;
11671
- return;
11672
- }
11673
- validatedEcosystems.push(ecosystem);
11674
- }
11677
+ const validatedEcosystems = utils.parseEcosystemChoices(utils.cmdFlagValueToArray(ecosystems), utils.getEcosystemChoicesForMeow());
11675
11678
 
11676
11679
  // Process and validate package manager values early, before dry-run check.
11677
11680
  // Coana normalizes input to uppercase and rejects unknown values, so do the
@@ -16256,13 +16259,6 @@ async function enumerateWorkspaces({
16256
16259
  // several POMs, so several roots, there.
16257
16260
 
16258
16261
  const ROOT_ECOSYSTEMS = ['maven', 'gradle', 'sbt', 'dotnet'];
16259
- const ECOSYSTEM_LABELS = {
16260
- __proto__: null,
16261
- dotnet: '.NET',
16262
- gradle: 'Gradle',
16263
- maven: 'Maven',
16264
- sbt: 'sbt'
16265
- };
16266
16262
 
16267
16263
  // The shallowest directory matching `--exclude-paths`, not necessarily a
16268
16264
  // project dir itself - one write here covers every project beneath it.
@@ -16435,12 +16431,6 @@ async function markWorkspaceCoverage({
16435
16431
  data: undefined
16436
16432
  };
16437
16433
  }
16438
- if (ecosystem === 'dotnet' && (await skipWithoutDotnetSdk(bin, dir))) {
16439
- return {
16440
- ok: true,
16441
- data: undefined
16442
- };
16443
- }
16444
16434
  for (const root of roots) {
16445
16435
  if (
16446
16436
  // eslint-disable-next-line no-await-in-loop
@@ -21521,6 +21511,7 @@ const generalFlags$1 = {
21521
21511
  default: false,
21522
21512
  description: `${DYNAMIC_SBOM_INFERENCE_DESCRIPTION} Combine with --reach to split the reachability analysis per project/module.`
21523
21513
  },
21514
+ ...dynamicSbomInferenceEcosystemsFlags,
21524
21515
  interactive: {
21525
21516
  type: 'boolean',
21526
21517
  default: true,
@@ -21673,7 +21664,6 @@ async function run$d(argv, importMeta, {
21673
21664
  committers,
21674
21665
  cwd: cwdOverride,
21675
21666
  defaultBranch,
21676
- dynamicSbomInference,
21677
21667
  interactive = true,
21678
21668
  json,
21679
21669
  markdown,
@@ -21708,6 +21698,7 @@ async function run$d(argv, importMeta, {
21708
21698
 
21709
21699
  // Validate ecosystem values against the reachability-supported set.
21710
21700
  const reachEcosystems = utils.parseReachEcosystems(utils.cmdFlagValueToArray(cli.flags['reachEcosystems']));
21701
+ const dynamicSbomInference = parseDynamicSbomInferenceFlags(cli.flags);
21711
21702
  warnSocketJsonOnlyFlags(cli.flags, `${parentName} ${CMD_NAME$a}`);
21712
21703
  const dryRun = !!cli.flags['dryRun'];
21713
21704
  const uvMembers = !!cli.flags['uvMembers'];
@@ -21949,7 +21940,7 @@ async function run$d(argv, importMeta, {
21949
21940
  pendingHead: Boolean(pendingHead),
21950
21941
  pullRequest: Number(pullRequest),
21951
21942
  reach: {
21952
- dynamicSbomInference: Boolean(dynamicSbomInference),
21943
+ dynamicSbomInference,
21953
21944
  excludePaths,
21954
21945
  reachAdvisories,
21955
21946
  reachAnalysisMemoryLimit,
@@ -22669,7 +22660,7 @@ async function scanOneRepo(repoSlug, {
22669
22660
  pendingHead: true,
22670
22661
  pullRequest: 0,
22671
22662
  reach: {
22672
- dynamicSbomInference: false,
22663
+ dynamicSbomInference: undefined,
22673
22664
  excludePaths: [],
22674
22665
  reachAdvisories: [],
22675
22666
  reachAnalysisMemoryLimit: '',
@@ -23874,18 +23865,17 @@ async function runScanReach({
23874
23865
  // Extra discovery targets beyond the user's own; the reachability target
23875
23866
  // itself stays `targets[0]`.
23876
23867
  let scanTargets = targets;
23877
- // Sidecar forwarded to reachability; populated by dynamic SBOM inference.
23878
- let resolvedPathsSidecar;
23868
+ let dynamicResult;
23879
23869
  if (reachabilityOptions.dynamicSbomInference) {
23880
- logger.logger.info('Generating Socket facts for each Gradle, sbt, Maven and .NET build root ...');
23881
- const dynamicResult = await runDynamicSbomInference({
23870
+ logger.logger.info('Generating Socket facts for each build root ...');
23871
+ dynamicResult = await runDynamicSbomInference({
23872
+ consumer: 'reach',
23882
23873
  cwd,
23883
23874
  excludePaths: reachabilityOptions.excludePaths,
23884
- sbtTmpDir,
23885
- withFiles: true
23875
+ purlTypes: reachabilityOptions.dynamicSbomInference,
23876
+ sbtTmpDir
23886
23877
  });
23887
23878
  scanTargets = Array.from(new Set([...scanTargets, ...dynamicResult.factsPaths]));
23888
- resolvedPathsSidecar = dynamicResult.resolvedPathsSidecar;
23889
23879
  }
23890
23880
 
23891
23881
  // Get supported file names.
@@ -23934,12 +23924,13 @@ async function runScanReach({
23934
23924
  spinner.start('Running reachability analysis...');
23935
23925
  const result = await performReachabilityAnalysis({
23936
23926
  cwd,
23927
+ factsOnlyTypes: dynamicResult?.factsOnlyTypes,
23937
23928
  orgSlug,
23938
23929
  outputKind,
23939
23930
  outputPath,
23940
23931
  packagePaths,
23941
23932
  reachabilityOptions: mergedReachabilityOptions,
23942
- resolvedPathsSidecar,
23933
+ resolvedPathsSidecar: dynamicResult?.resolvedPathsSidecar,
23943
23934
  spinner,
23944
23935
  target: targets[0]
23945
23936
  });
@@ -23985,6 +23976,7 @@ const reachabilityFlagsForReach = {
23985
23976
  default: false,
23986
23977
  description: `${DYNAMIC_SBOM_INFERENCE_DESCRIPTION} The reachability analysis is split per project/module accordingly.`
23987
23978
  },
23979
+ ...dynamicSbomInferenceEcosystemsFlags,
23988
23980
  // Fallback targets scan creation, which this command never performs.
23989
23981
  reachFallbackToRegularScan: {
23990
23982
  type: 'boolean',
@@ -24072,7 +24064,6 @@ async function run$7(argv, importMeta, {
24072
24064
  warnSocketJsonOnlyFlags(cli.flags, `${parentName} ${CMD_NAME$4}`);
24073
24065
  const {
24074
24066
  cwd: cwdOverride,
24075
- dynamicSbomInference,
24076
24067
  interactive = true,
24077
24068
  json,
24078
24069
  markdown,
@@ -24109,6 +24100,7 @@ async function run$7(argv, importMeta, {
24109
24100
 
24110
24101
  // Validate ecosystem values against the reachability-supported set.
24111
24102
  const reachEcosystems = utils.parseReachEcosystems(reachEcosystemsRaw);
24103
+ const dynamicSbomInference = parseDynamicSbomInferenceFlags(cli.flags);
24112
24104
  const processCwd = process.cwd();
24113
24105
  const cwd = cwdOverride && cwdOverride !== '.' && cwdOverride !== processCwd ? path.resolve(processCwd, cwdOverride) : processCwd;
24114
24106
 
@@ -24183,7 +24175,7 @@ async function run$7(argv, importMeta, {
24183
24175
  outputKind,
24184
24176
  outputPath: outputPath || '',
24185
24177
  reachabilityOptions: {
24186
- dynamicSbomInference: Boolean(dynamicSbomInference),
24178
+ dynamicSbomInference,
24187
24179
  excludePaths,
24188
24180
  reachAdvisories,
24189
24181
  reachAnalysisMemoryLimit,
@@ -26151,5 +26143,5 @@ process.on('unhandledRejection', async (reason, promise) => {
26151
26143
  // eslint-disable-next-line n/no-process-exit
26152
26144
  process.exit(1);
26153
26145
  });
26154
- //# debugId=1216e328-379a-4a10-9a67-9077254e95f9
26146
+ //# debugId=e8c87646-0d74-4bcc-9e38-e86a951f03ae
26155
26147
  //# sourceMappingURL=cli.js.map