diff --git a/package-lock.json b/package-lock.json index 84237ecb4..1f755eefe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@babel/preset-typescript": "7.27.1", "@babel/runtime": "7.28.3", "@biomejs/biome": "2.2.0", - "@coana-tech/cli": "14.11.8", + "@coana-tech/cli": "14.11.10", "@cyclonedx/cdxgen": "11.5.0", "@dotenvx/dotenvx": "1.48.4", "@eslint/compat": "1.3.2", @@ -898,9 +898,9 @@ "optional": true }, "node_modules/@coana-tech/cli": { - "version": "14.11.8", - "resolved": "https://registry.npmjs.org/@coana-tech/cli/-/cli-14.11.8.tgz", - "integrity": "sha512-27fAPgKzXQtXgaueUcM6YTXmgu5qPqsCaoWpkRxGSQseSzY33N4ZuHwMrim+4DOiqqU/vdHIxhzD0RP+c+UR7Q==", + "version": "14.11.10", + "resolved": "https://registry.npmjs.org/@coana-tech/cli/-/cli-14.11.10.tgz", + "integrity": "sha512-fk9pZf4UbEgdW6VhbW97VsO7R+PKXpg2L9gzXOasUEZq0K1B13cRp9mrLFULurAxrXD1dXF7Pw0xLADI9n3DmA==", "dev": true, "bin": { "cli": "cli-wrapper.mjs" diff --git a/package.json b/package.json index 32c580735..f66f18492 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@babel/preset-typescript": "7.27.1", "@babel/runtime": "7.28.3", "@biomejs/biome": "2.2.0", - "@coana-tech/cli": "14.11.8", + "@coana-tech/cli": "14.11.10", "@cyclonedx/cdxgen": "11.5.0", "@dotenvx/dotenvx": "1.48.4", "@eslint/compat": "1.3.2", diff --git a/src/commands/ci/handle-ci.mts b/src/commands/ci/handle-ci.mts index b0a684eec..156410eb7 100644 --- a/src/commands/ci/handle-ci.mts +++ b/src/commands/ci/handle-ci.mts @@ -39,7 +39,6 @@ export async function handleCi(autoManifest: boolean): Promise { pullRequest: 0, reach: { runReachabilityAnalysis: false, - reachContinueOnFailingProjects: false, reachDisableAnalytics: false, reachAnalysisTimeout: 0, reachAnalysisMemoryLimit: 0, diff --git a/src/commands/scan/cmd-scan-create.mts b/src/commands/scan/cmd-scan-create.mts index 2664f7ef9..dc6622cac 100644 --- a/src/commands/scan/cmd-scan-create.mts +++ b/src/commands/scan/cmd-scan-create.mts @@ -218,7 +218,6 @@ async function run( reach, reachAnalysisMemoryLimit, reachAnalysisTimeout, - reachContinueOnFailingProjects, reachDisableAnalytics, readOnly, setAsAlertsPage: pendingHeadFlag, @@ -241,7 +240,6 @@ async function run( reach: boolean reachAnalysisTimeout: number reachAnalysisMemoryLimit: number - reachContinueOnFailingProjects: boolean reachDisableAnalytics: boolean } @@ -467,13 +465,6 @@ async function run( message: 'The --reachEcosystems flag requires --reach to be set', fail: 'missing --reach flag', }, - { - nook: true, - test: reach || !reachContinueOnFailingProjects, - message: - 'The --reachContinueOnFailingProjects flag requires --reach to be set', - fail: 'missing --reach flag', - }, { nook: true, test: reach || !reachExcludePaths.length, @@ -505,7 +496,6 @@ async function run( pullRequest: Number(pullRequest), reach: { runReachabilityAnalysis: Boolean(reach), - reachContinueOnFailingProjects: Boolean(reachContinueOnFailingProjects), reachDisableAnalytics: Boolean(reachDisableAnalytics), reachAnalysisTimeout: Number(reachAnalysisTimeout), reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit), diff --git a/src/commands/scan/cmd-scan-create.test.mts b/src/commands/scan/cmd-scan-create.test.mts index 062204aa3..01b98c3dc 100644 --- a/src/commands/scan/cmd-scan-create.test.mts +++ b/src/commands/scan/cmd-scan-create.test.mts @@ -45,7 +45,6 @@ describe('socket scan create', async () => { Reachability Options (when --reach is used) --reach-analysis-memory-limit The maximum memory in MB to use for the reachability analysis. The default is 8192MB. --reach-analysis-timeout Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly. - --reach-continue-on-failing-projects Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace. --reach-disable-analytics Disable reachability analytics sharing with Socket. Also disables caching-based optimizations. --reach-ecosystems List of ecosystems to conduct reachability analysis on, as either a comma separated value or as multiple flags. Defaults to all ecosystems. --reach-exclude-paths List of paths to exclude from reachability analysis, as either a comma separated value or as multiple flags. @@ -285,37 +284,6 @@ describe('socket scan create', async () => { }, ) - cmdit( - [ - 'scan', - 'create', - '--org', - 'fakeOrg', - 'target', - '--dry-run', - '--repo', - 'xyz', - '--branch', - 'abc', - '--reach-continue-on-failing-projects', - '--config', - '{"apiToken":"fakeToken"}', - ], - 'should fail when --reach-continue-on-failing-projects is used without --reach', - async cmd => { - const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) - const output = stdout + stderr - expect(output).toContain( - 'The --reachContinueOnFailingProjects flag requires --reach to be set', - ) - expect(output).toContain('missing --reach flag') - expect( - code, - 'should exit with non-zero code when validation fails', - ).not.toBe(0) - }, - ) - cmdit( [ 'scan', @@ -394,39 +362,6 @@ describe('socket scan create', async () => { '--branch', 'abc', '--reach', - '--reach-continue-on-failing-projects', - '--reach-disable-analytics', - '--reach-analysis-memory-limit', - '4096', - '--reach-analysis-timeout', - '3600', - '--reach-ecosystems', - 'npm', - '--config', - '{"apiToken":"fakeToken"}', - ], - 'should succeed when all reachability options including reachContinueOnFailingProjects are used with --reach', - async cmd => { - const { code, stdout } = await invokeNpm(binCliPath, cmd) - expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) - expect(code, 'should exit with code 0 when all flags are valid').toBe(0) - }, - ) - - cmdit( - [ - 'scan', - 'create', - '--org', - 'fakeOrg', - 'target', - '--dry-run', - '--repo', - 'xyz', - '--branch', - 'abc', - '--reach', - '--reach-continue-on-failing-projects', '--reach-disable-analytics', '--reach-analysis-memory-limit', '4096', diff --git a/src/commands/scan/cmd-scan-reach.mts b/src/commands/scan/cmd-scan-reach.mts index a208a06b9..8576c143d 100644 --- a/src/commands/scan/cmd-scan-reach.mts +++ b/src/commands/scan/cmd-scan-reach.mts @@ -50,6 +50,10 @@ const config: CliCommandConfig = { Usage $ ${command} [options] [CWD=.] + API Token Requirements + - Quota: 1 unit + - Permissions: full-scans:create + Options ${getFlagListOutput(generalFlags)} @@ -96,7 +100,6 @@ async function run( org: orgFlag, reachAnalysisMemoryLimit, reachAnalysisTimeout, - reachContinueOnFailingProjects, reachDisableAnalytics, } = cli.flags as { cwd: string @@ -106,7 +109,6 @@ async function run( org: string reachAnalysisTimeout: number reachAnalysisMemoryLimit: number - reachContinueOnFailingProjects: boolean reachDisableAnalytics: boolean } @@ -183,7 +185,6 @@ async function run( targets, interactive, reachabilityOptions: { - reachContinueOnFailingProjects: Boolean(reachContinueOnFailingProjects), reachDisableAnalytics: Boolean(reachDisableAnalytics), reachAnalysisTimeout: Number(reachAnalysisTimeout), reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit), diff --git a/src/commands/scan/cmd-scan-reach.test.mts b/src/commands/scan/cmd-scan-reach.test.mts index f5e97c81f..2fbadb0b1 100644 --- a/src/commands/scan/cmd-scan-reach.test.mts +++ b/src/commands/scan/cmd-scan-reach.test.mts @@ -18,6 +18,10 @@ describe('socket scan reach', async () => { Usage $ socket scan reach [options] [CWD=.] + API Token Requirements + - Quota: 1 unit + - Permissions: full-scans:create + Options --cwd working directory, defaults to process.cwd() --json Output result as json @@ -27,7 +31,6 @@ describe('socket scan reach', async () => { Reachability Options --reach-analysis-memory-limit The maximum memory in MB to use for the reachability analysis. The default is 8192MB. --reach-analysis-timeout Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly. - --reach-continue-on-failing-projects Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace. --reach-disable-analytics Disable reachability analytics sharing with Socket. Also disables caching-based optimizations. --reach-ecosystems List of ecosystems to conduct reachability analysis on, as either a comma separated value or as multiple flags. Defaults to all ecosystems. --reach-exclude-paths List of paths to exclude from reachability analysis, as either a comma separated value or as multiple flags. @@ -206,25 +209,6 @@ describe('socket scan reach', async () => { }, ) - cmdit( - [ - 'scan', - 'reach', - '--dry-run', - '--reach-continue-on-failing-projects', - '--org', - 'fakeOrg', - '--config', - '{"apiToken":"fakeToken"}', - ], - 'should accept --reach-continue-on-failing-projects flag', - async cmd => { - const { code, stdout } = await invokeNpm(binCliPath, cmd) - expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) - expect(code, 'should exit with code 0').toBe(0) - }, - ) - cmdit( [ 'scan', @@ -279,7 +263,6 @@ describe('socket scan reach', async () => { '3600', '--reach-ecosystems', 'npm,pypi', - '--reach-continue-on-failing-projects', '--reach-exclude-paths', 'node_modules,dist', '--org', diff --git a/src/commands/scan/create-scan-from-github.mts b/src/commands/scan/create-scan-from-github.mts index 9b10f1394..498d127f2 100644 --- a/src/commands/scan/create-scan-from-github.mts +++ b/src/commands/scan/create-scan-from-github.mts @@ -241,7 +241,6 @@ async function scanOneRepo( pullRequest: 0, reach: { runReachabilityAnalysis: false, - reachContinueOnFailingProjects: false, reachDisableAnalytics: false, reachAnalysisTimeout: 0, reachAnalysisMemoryLimit: 0, diff --git a/src/commands/scan/handle-create-new-scan.mts b/src/commands/scan/handle-create-new-scan.mts index 5e419c056..07eb5c656 100644 --- a/src/commands/scan/handle-create-new-scan.mts +++ b/src/commands/scan/handle-create-new-scan.mts @@ -7,23 +7,18 @@ import { fetchSupportedScanFileNames } from './fetch-supported-scan-file-names.m import { finalizeTier1Scan } from './finalize-tier1-scan.mts' import { handleScanReport } from './handle-scan-report.mts' import { outputCreateNewScan } from './output-create-new-scan.mts' +import { + type ReachabilityOptions, + performReachabilityAnalysis, +} from './perform-reachability-analysis.mts' import constants from '../../constants.mts' -import { handleApiCall } from '../../utils/api.mts' import { checkCommandInput } from '../../utils/check-input.mts' -import { - extractTier1ReachabilityScanId, - spawnCoana, -} from '../../utils/coana.mts' -import { convertToCoanaEcosystems } from '../../utils/ecosystem.mts' import { getPackageFilesForScan } from '../../utils/path-resolve.mts' -import { setupSdk } from '../../utils/sdk.mts' import { readOrDefaultSocketJson } from '../../utils/socket-json.mts' import { detectManifestActions } from '../manifest/detect-manifest-actions.mts' import { generateAutoManifest } from '../manifest/generate_auto_manifest.mts' -import type { CResult, OutputKind } from '../../types.mts' -import type { PURL_Type } from '../../utils/ecosystem.mts' -import type { Spinner } from '@socketsecurity/registry/lib/spinner' +import type { OutputKind } from '../../types.mts' export async function handleCreateNewScan({ autoManifest, @@ -59,13 +54,7 @@ export async function handleCreateNewScan({ outputKind: OutputKind reach: { runReachabilityAnalysis: boolean - reachContinueOnFailingProjects: boolean - reachDisableAnalytics: boolean - reachAnalysisTimeout: number - reachAnalysisMemoryLimit: number - reachEcosystems: PURL_Type[] - reachExcludePaths: string[] - } + } & ReachabilityOptions readOnly: boolean repoName: string report: boolean @@ -159,7 +148,9 @@ export async function handleCreateNewScan({ logger.success('Reachability analysis completed successfully') - scanPaths = reachResult.data?.scanPaths || [] + scanPaths = reachResult.data?.reachabilityReport + ? [reachResult.data.reachabilityReport] + : [] tier1ReachabilityScanId = reachResult.data?.tier1ReachabilityScanId } @@ -224,166 +215,3 @@ export async function handleCreateNewScan({ await outputCreateNewScan(fullScanCResult, { interactive, outputKind }) } } - -type ReachabilityAnalysisConfig = { - branchName: string - cwd: string - orgSlug: string - packagePaths: string[] - reachabilityOptions: { - reachContinueOnFailingProjects: boolean - reachDisableAnalytics: boolean - reachAnalysisTimeout: number - reachAnalysisMemoryLimit: number - reachEcosystems: PURL_Type[] - reachExcludePaths: string[] - } - repoName: string -} - -type ReachabilityAnalysisOptions = { - spinner?: Spinner | undefined -} - -type ReachabilityAnalysisResult = { - scanPaths: string[] - tier1ReachabilityScanId: string | undefined -} - -async function performReachabilityAnalysis( - { - branchName, - cwd, - orgSlug, - packagePaths, - reachabilityOptions, - repoName, - }: ReachabilityAnalysisConfig, - options?: ReachabilityAnalysisOptions | undefined, -): Promise> { - const { spinner } = { - __proto__: null, - ...options, - } as ReachabilityAnalysisOptions - - // Setup SDK for uploading manifests - const sockSdkCResult = await setupSdk() - if (!sockSdkCResult.ok) { - return sockSdkCResult - } - - const sockSdk = sockSdkCResult.data - - const wasSpinning = !!spinner?.isSpinning - - // Upload manifests to get tar hash - spinner?.start('Uploading manifests for reachability analysis...') - - // Exclude DOT_SOCKET_DOT_FACTS_JSON from previous runs. - const filteredPackagePaths = packagePaths.filter( - p => !p.endsWith(constants.DOT_SOCKET_DOT_FACTS_JSON), - ) - const uploadCResult = await handleApiCall( - sockSdk.uploadManifestFiles(orgSlug, filteredPackagePaths), - { - desc: 'upload manifests', - spinner, - }, - ) - - spinner?.stop() - - if (!uploadCResult.ok) { - if (wasSpinning) { - spinner.start() - } - return uploadCResult - } - - const tarHash = (uploadCResult.data as { tarHash?: string })?.tarHash - if (!tarHash) { - if (wasSpinning) { - spinner.start() - } - return { - ok: false, - message: 'Failed to get manifest tar hash', - cause: 'Server did not return a tar hash for the uploaded manifests', - } - } - - spinner?.start() - spinner?.success(`Manifests uploaded successfully. Tar hash: ${tarHash}`) - spinner?.infoAndStop('Running reachability analysis with Coana...') - - // Run Coana with the manifests tar hash. - const coanaResult = await spawnCoana( - [ - 'run', - cwd, - '--output-dir', - cwd, - '--socket-mode', - constants.DOT_SOCKET_DOT_FACTS_JSON, - '--disable-report-submission', - ...(reachabilityOptions.reachAnalysisTimeout - ? [ - '--analysis-timeout', - reachabilityOptions.reachAnalysisTimeout.toString(), - ] - : []), - ...(reachabilityOptions.reachAnalysisMemoryLimit - ? [ - '--memory-limit', - reachabilityOptions.reachAnalysisMemoryLimit.toString(), - ] - : []), - ...(reachabilityOptions.reachDisableAnalytics - ? ['--disable-analytics-sharing'] - : []), - ...(reachabilityOptions.reachContinueOnFailingProjects - ? ['--ignore-failing-workspaces'] - : []), - // empty reachEcosystems implies scan all ecosystems - ...(reachabilityOptions.reachEcosystems.length - ? [ - '--ecosystems', - convertToCoanaEcosystems(reachabilityOptions.reachEcosystems).join( - ' ', - ), - ] - : []), - ...(reachabilityOptions.reachExcludePaths.length - ? ['--exclude-dirs', reachabilityOptions.reachExcludePaths.join(' ')] - : []), - '--manifests-tar-hash', - tarHash, - ], - { - cwd, - env: { - ...process.env, - SOCKET_REPO_NAME: repoName, - SOCKET_BRANCH_NAME: branchName, - }, - spinner, - stdio: 'inherit', - }, - ) - - if (wasSpinning) { - spinner.start() - } - return coanaResult.ok - ? { - ok: true, - data: { - // Use the DOT_SOCKET_DOT_FACTS_JSON file for the scan. - scanPaths: [constants.DOT_SOCKET_DOT_FACTS_JSON], - tier1ReachabilityScanId: extractTier1ReachabilityScanId( - constants.DOT_SOCKET_DOT_FACTS_JSON, - ), - }, - } - : coanaResult -} diff --git a/src/commands/scan/output-scan-reach.mts b/src/commands/scan/output-scan-reach.mts index 97aee4184..691610735 100644 --- a/src/commands/scan/output-scan-reach.mts +++ b/src/commands/scan/output-scan-reach.mts @@ -6,12 +6,13 @@ import constants from '../../constants.mts' import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' import { serializeResultJson } from '../../utils/serialize-result-json.mts' +import type { ReachabilityAnalysisResult } from './perform-reachability-analysis.mts' import type { CResult, OutputKind } from '../../types.mts' const { DOT_SOCKET_DOT_FACTS_JSON } = constants export async function outputScanReach( - result: CResult, + result: CResult, { cwd, outputKind }: { cwd: string; outputKind: OutputKind }, ): Promise { if (!result.ok) { diff --git a/src/commands/scan/perform-reachability-analysis.mts b/src/commands/scan/perform-reachability-analysis.mts index ec4fb6186..99c792adb 100644 --- a/src/commands/scan/perform-reachability-analysis.mts +++ b/src/commands/scan/perform-reachability-analysis.mts @@ -4,15 +4,13 @@ import { extractTier1ReachabilityScanId, spawnCoana, } from '../../utils/coana.mts' -import { convertToCoanaEcosystems } from '../../utils/ecosystem.mts' import { setupSdk } from '../../utils/sdk.mts' import type { CResult } from '../../types.mts' -import type { PURL_Type } from '../../utils/ecosystem.mts' import type { Spinner } from '@socketsecurity/registry/lib/spinner' +import type { PURL_Type } from '../../utils/ecosystem.mts' export type ReachabilityOptions = { - reachContinueOnFailingProjects: boolean reachDisableAnalytics: boolean reachAnalysisTimeout: number reachAnalysisMemoryLimit: number @@ -35,7 +33,7 @@ export type ReachabilityAnalysisOptions = { } export type ReachabilityAnalysisResult = { - scanPaths: string[] + reachabilityReport: string tier1ReachabilityScanId: string | undefined } @@ -140,15 +138,9 @@ export async function performReachabilityAnalysis( ...(reachabilityOptions.reachDisableAnalytics ? ['--disable-analytics-sharing'] : []), - ...(reachabilityOptions.reachContinueOnFailingProjects - ? ['--ignore-failing-workspaces'] - : []), // empty reachEcosystems implies scan all ecosystems ...(reachabilityOptions.reachEcosystems.length - ? [ - '--ecosystems', - ...convertToCoanaEcosystems(reachabilityOptions.reachEcosystems), - ] + ? ['--purl-types', ...reachabilityOptions.reachEcosystems] : []), ...(reachabilityOptions.reachExcludePaths.length ? ['--exclude-dirs', reachabilityOptions.reachExcludePaths.join(' ')] @@ -187,7 +179,7 @@ export async function performReachabilityAnalysis( ok: true, data: { // Use the DOT_SOCKET_DOT_FACTS_JSON file for the scan. - scanPaths: [constants.DOT_SOCKET_DOT_FACTS_JSON], + reachabilityReport: constants.DOT_SOCKET_DOT_FACTS_JSON, tier1ReachabilityScanId: extractTier1ReachabilityScanId( constants.DOT_SOCKET_DOT_FACTS_JSON, ), diff --git a/src/commands/scan/reachability-flags.mts b/src/commands/scan/reachability-flags.mts index 05736f831..ba430ac9b 100644 --- a/src/commands/scan/reachability-flags.mts +++ b/src/commands/scan/reachability-flags.mts @@ -13,11 +13,6 @@ export const reachabilityFlags: MeowFlags = { description: 'Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly.', }, - reachContinueOnFailingProjects: { - type: 'boolean', - description: - 'Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace.', - }, reachDisableAnalytics: { type: 'boolean', default: false, diff --git a/src/utils/ecosystem.mts b/src/utils/ecosystem.mts index 942af7a96..33a977a5c 100644 --- a/src/utils/ecosystem.mts +++ b/src/utils/ecosystem.mts @@ -55,53 +55,8 @@ export type _Check_ALL_ECOSYSTEMS_has_all_purl_types = export type _Check_ALL_ECOSYSTEMS_has_no_extras = ExpectNever -const COANA_SUPPORTED_LIST = [ - 'composer', - 'hex', - 'github', - 'golang', - 'maven', - 'npm', - 'nuget', - 'pypi', - 'pub', - 'gem', - 'cargo', - 'swift', -] as const satisfies readonly PURL_Type[] - export const ALL_SUPPORTED_ECOSYSTEMS = new Set(ALL_ECOSYSTEMS) -export const COANA_SUPPORTED_ECOSYSTEMS = new Set(COANA_SUPPORTED_LIST) - -/** - * Ecosystems/Purl types are slightly different in Coana. This function converts - * the PURL_Type[] to a string of Coana compatible ecosystem names. Ecosystems that - * are not supported by Coana are ignored. - */ -export function convertToCoanaEcosystems(ecosystems: PURL_Type[]): string[] { - return ecosystems - .filter(ecosystem => COANA_SUPPORTED_ECOSYSTEMS.has(ecosystem as PURL_Type)) - .map(ecosystem => { - switch (ecosystem) { - case 'cargo': - return 'RUST' - case 'gem': - return 'RUBYGEMS' - case 'github': - return 'ACTIONS' - case 'golang': - return 'GO' - case 'hex': - return 'ERLANG' - case 'pypi': - return 'PIP' - default: - return ecosystem.toUpperCase() - } - }) -} - export function getEcosystemChoicesForMeow(): string[] { return [...ALL_ECOSYSTEMS] }