diff --git a/CHANGELOG.md b/CHANGELOG.md index ac196b1e2..8785db577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [Unreleased] + +### Changed +- Updated Coana CLI to v14.12.148. +- Updated to @socketsecurity/socket-patch@1.2.0. + +### Fixed +- Prevent heap overflow in large monorepo scans by using streaming-based filtering to avoid accumulating all file paths in memory before filtering. + ## [2.1.0](https://github.com/SocketDev/socket-cli/releases/tag/v2.1.0) - 2025-11-02 ### Added diff --git a/packages/cli/external-tools.json b/packages/cli/external-tools.json index 20a1429fc..a9c29c5ff 100644 --- a/packages/cli/external-tools.json +++ b/packages/cli/external-tools.json @@ -3,7 +3,7 @@ "description": "Coana CLI for static analysis and reachability detection", "type": "npm", "package": "@coana-tech/cli", - "version": "14.12.139" + "version": "14.12.148" }, "@cyclonedx/cdxgen": { "description": "CycloneDX SBOM generator for software bill of materials", @@ -24,6 +24,12 @@ "package": "socketsecurity", "version": "^2.2.15" }, + "socket-patch": { + "description": "Socket Patch CLI for applying security patches", + "type": "npm", + "package": "@socketsecurity/socket-patch", + "version": "1.2.0" + }, "sfw": { "description": "Socket Firewall (sfw)", "type": "standalone", diff --git a/packages/cli/src/commands/patch/PatchSelectorApp.tsx b/packages/cli/src/commands/patch-old/PatchSelectorApp.tsx similarity index 100% rename from packages/cli/src/commands/patch/PatchSelectorApp.tsx rename to packages/cli/src/commands/patch-old/PatchSelectorApp.tsx diff --git a/packages/cli/src/commands/patch/cmd-patch-apply.mts b/packages/cli/src/commands/patch-old/cmd-patch-apply.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-apply.mts rename to packages/cli/src/commands/patch-old/cmd-patch-apply.mts diff --git a/packages/cli/src/commands/patch/cmd-patch-cleanup.mts b/packages/cli/src/commands/patch-old/cmd-patch-cleanup.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-cleanup.mts rename to packages/cli/src/commands/patch-old/cmd-patch-cleanup.mts diff --git a/packages/cli/src/commands/patch/cmd-patch-discover.mts b/packages/cli/src/commands/patch-old/cmd-patch-discover.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-discover.mts rename to packages/cli/src/commands/patch-old/cmd-patch-discover.mts diff --git a/packages/cli/src/commands/patch/cmd-patch-download.mts b/packages/cli/src/commands/patch-old/cmd-patch-download.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-download.mts rename to packages/cli/src/commands/patch-old/cmd-patch-download.mts diff --git a/packages/cli/src/commands/patch/cmd-patch-get.mts b/packages/cli/src/commands/patch-old/cmd-patch-get.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-get.mts rename to packages/cli/src/commands/patch-old/cmd-patch-get.mts diff --git a/packages/cli/src/commands/patch/cmd-patch-info.mts b/packages/cli/src/commands/patch-old/cmd-patch-info.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-info.mts rename to packages/cli/src/commands/patch-old/cmd-patch-info.mts diff --git a/packages/cli/src/commands/patch/cmd-patch-list.mts b/packages/cli/src/commands/patch-old/cmd-patch-list.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-list.mts rename to packages/cli/src/commands/patch-old/cmd-patch-list.mts diff --git a/packages/cli/src/commands/patch/cmd-patch-rm.mts b/packages/cli/src/commands/patch-old/cmd-patch-rm.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-rm.mts rename to packages/cli/src/commands/patch-old/cmd-patch-rm.mts diff --git a/packages/cli/src/commands/patch/cmd-patch-status.mts b/packages/cli/src/commands/patch-old/cmd-patch-status.mts similarity index 100% rename from packages/cli/src/commands/patch/cmd-patch-status.mts rename to packages/cli/src/commands/patch-old/cmd-patch-status.mts diff --git a/packages/cli/src/commands/patch-old/cmd-patch.mts b/packages/cli/src/commands/patch-old/cmd-patch.mts new file mode 100644 index 000000000..5e5ace8c5 --- /dev/null +++ b/packages/cli/src/commands/patch-old/cmd-patch.mts @@ -0,0 +1,45 @@ +import { cmdPatchApply } from './cmd-patch-apply.mts' +import { cmdPatchCleanup } from './cmd-patch-cleanup.mts' +import { cmdPatchDiscover } from './cmd-patch-discover.mts' +import { cmdPatchDownload } from './cmd-patch-download.mts' +import { cmdPatchGet } from './cmd-patch-get.mts' +import { cmdPatchInfo } from './cmd-patch-info.mts' +import { cmdPatchList } from './cmd-patch-list.mts' +import { cmdPatchRm } from './cmd-patch-rm.mts' +import { cmdPatchStatus } from './cmd-patch-status.mts' +import { meowWithSubcommands } from '../../utils/cli/with-subcommands.mjs' + +import type { CliSubcommand } from '../../utils/cli/with-subcommands.mjs' + +const description = 'Manage CVE patches for dependencies' + +const hidden = false + +export const cmdPatch: CliSubcommand = { + description, + hidden, + async run(argv, importMeta, { parentName }) { + await meowWithSubcommands( + { + argv, + name: `${parentName} patch`, + importMeta, + subcommands: { + apply: cmdPatchApply, + cleanup: cmdPatchCleanup, + discover: cmdPatchDiscover, + download: cmdPatchDownload, + get: cmdPatchGet, + info: cmdPatchInfo, + list: cmdPatchList, + rm: cmdPatchRm, + status: cmdPatchStatus, + }, + }, + { + defaultSub: 'discover', + description, + }, + ) + }, +} diff --git a/packages/cli/src/commands/patch/handle-patch-apply.mts b/packages/cli/src/commands/patch-old/handle-patch-apply.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-apply.mts rename to packages/cli/src/commands/patch-old/handle-patch-apply.mts diff --git a/packages/cli/src/commands/patch/handle-patch-cleanup.mts b/packages/cli/src/commands/patch-old/handle-patch-cleanup.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-cleanup.mts rename to packages/cli/src/commands/patch-old/handle-patch-cleanup.mts diff --git a/packages/cli/src/commands/patch/handle-patch-discover.mts b/packages/cli/src/commands/patch-old/handle-patch-discover.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-discover.mts rename to packages/cli/src/commands/patch-old/handle-patch-discover.mts diff --git a/packages/cli/src/commands/patch/handle-patch-download.mts b/packages/cli/src/commands/patch-old/handle-patch-download.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-download.mts rename to packages/cli/src/commands/patch-old/handle-patch-download.mts diff --git a/packages/cli/src/commands/patch/handle-patch-get.mts b/packages/cli/src/commands/patch-old/handle-patch-get.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-get.mts rename to packages/cli/src/commands/patch-old/handle-patch-get.mts diff --git a/packages/cli/src/commands/patch/handle-patch-info.mts b/packages/cli/src/commands/patch-old/handle-patch-info.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-info.mts rename to packages/cli/src/commands/patch-old/handle-patch-info.mts diff --git a/packages/cli/src/commands/patch/handle-patch-list.mts b/packages/cli/src/commands/patch-old/handle-patch-list.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-list.mts rename to packages/cli/src/commands/patch-old/handle-patch-list.mts diff --git a/packages/cli/src/commands/patch/handle-patch-rm.mts b/packages/cli/src/commands/patch-old/handle-patch-rm.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-rm.mts rename to packages/cli/src/commands/patch-old/handle-patch-rm.mts diff --git a/packages/cli/src/commands/patch/handle-patch-status.mts b/packages/cli/src/commands/patch-old/handle-patch-status.mts similarity index 100% rename from packages/cli/src/commands/patch/handle-patch-status.mts rename to packages/cli/src/commands/patch-old/handle-patch-status.mts diff --git a/packages/cli/src/commands/patch/manifest-schema.mts b/packages/cli/src/commands/patch-old/manifest-schema.mts similarity index 100% rename from packages/cli/src/commands/patch/manifest-schema.mts rename to packages/cli/src/commands/patch-old/manifest-schema.mts diff --git a/packages/cli/src/commands/patch/output-patch-cleanup-result.mts b/packages/cli/src/commands/patch-old/output-patch-cleanup-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-cleanup-result.mts rename to packages/cli/src/commands/patch-old/output-patch-cleanup-result.mts diff --git a/packages/cli/src/commands/patch/output-patch-discover-result.mts b/packages/cli/src/commands/patch-old/output-patch-discover-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-discover-result.mts rename to packages/cli/src/commands/patch-old/output-patch-discover-result.mts diff --git a/packages/cli/src/commands/patch/output-patch-download-result.mts b/packages/cli/src/commands/patch-old/output-patch-download-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-download-result.mts rename to packages/cli/src/commands/patch-old/output-patch-download-result.mts diff --git a/packages/cli/src/commands/patch/output-patch-get-result.mts b/packages/cli/src/commands/patch-old/output-patch-get-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-get-result.mts rename to packages/cli/src/commands/patch-old/output-patch-get-result.mts diff --git a/packages/cli/src/commands/patch/output-patch-info-result.mts b/packages/cli/src/commands/patch-old/output-patch-info-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-info-result.mts rename to packages/cli/src/commands/patch-old/output-patch-info-result.mts diff --git a/packages/cli/src/commands/patch/output-patch-list-result.mts b/packages/cli/src/commands/patch-old/output-patch-list-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-list-result.mts rename to packages/cli/src/commands/patch-old/output-patch-list-result.mts diff --git a/packages/cli/src/commands/patch/output-patch-result.mts b/packages/cli/src/commands/patch-old/output-patch-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-result.mts rename to packages/cli/src/commands/patch-old/output-patch-result.mts diff --git a/packages/cli/src/commands/patch/output-patch-rm-result.mts b/packages/cli/src/commands/patch-old/output-patch-rm-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-rm-result.mts rename to packages/cli/src/commands/patch-old/output-patch-rm-result.mts diff --git a/packages/cli/src/commands/patch/output-patch-status-result.mts b/packages/cli/src/commands/patch-old/output-patch-status-result.mts similarity index 100% rename from packages/cli/src/commands/patch/output-patch-status-result.mts rename to packages/cli/src/commands/patch-old/output-patch-status-result.mts diff --git a/packages/cli/src/commands/patch/cmd-patch.mts b/packages/cli/src/commands/patch/cmd-patch.mts index 5e5ace8c5..c6704aeeb 100644 --- a/packages/cli/src/commands/patch/cmd-patch.mts +++ b/packages/cli/src/commands/patch/cmd-patch.mts @@ -1,15 +1,9 @@ -import { cmdPatchApply } from './cmd-patch-apply.mts' -import { cmdPatchCleanup } from './cmd-patch-cleanup.mts' -import { cmdPatchDiscover } from './cmd-patch-discover.mts' -import { cmdPatchDownload } from './cmd-patch-download.mts' -import { cmdPatchGet } from './cmd-patch-get.mts' -import { cmdPatchInfo } from './cmd-patch-info.mts' -import { cmdPatchList } from './cmd-patch-list.mts' -import { cmdPatchRm } from './cmd-patch-rm.mts' -import { cmdPatchStatus } from './cmd-patch-status.mts' -import { meowWithSubcommands } from '../../utils/cli/with-subcommands.mjs' +import { spawnSocketPatch } from '../../utils/socket-patch/spawn.mts' -import type { CliSubcommand } from '../../utils/cli/with-subcommands.mjs' +import type { + CliCommandContext, + CliSubcommand, +} from '../../utils/cli/with-subcommands.mjs' const description = 'Manage CVE patches for dependencies' @@ -18,28 +12,18 @@ const hidden = false export const cmdPatch: CliSubcommand = { description, hidden, - async run(argv, importMeta, { parentName }) { - await meowWithSubcommands( - { - argv, - name: `${parentName} patch`, - importMeta, - subcommands: { - apply: cmdPatchApply, - cleanup: cmdPatchCleanup, - discover: cmdPatchDiscover, - download: cmdPatchDownload, - get: cmdPatchGet, - info: cmdPatchInfo, - list: cmdPatchList, - rm: cmdPatchRm, - status: cmdPatchStatus, - }, - }, - { - defaultSub: 'discover', - description, - }, - ) - }, + run, +} + +async function run( + argv: string[] | readonly string[], + _importMeta: ImportMeta, + _context: CliCommandContext, +): Promise { + // Forward all arguments to socket-patch. + const result = await spawnSocketPatch([...argv]) + + if (!result.ok) { + process.exitCode = 1 + } } diff --git a/packages/cli/src/constants/env.mts b/packages/cli/src/constants/env.mts index 57139a5c9..c4bb9cec4 100644 --- a/packages/cli/src/constants/env.mts +++ b/packages/cli/src/constants/env.mts @@ -45,6 +45,7 @@ import { SOCKET_CLI_BOOTSTRAP_SPEC } from '../env/socket-cli-bootstrap-spec.mts' import { SOCKET_CLI_CDXGEN_LOCAL_PATH } from '../env/socket-cli-cdxgen-local-path.mts' import { SOCKET_CLI_COANA_LOCAL_PATH } from '../env/socket-cli-coana-local-path.mts' import { SOCKET_CLI_CONFIG } from '../env/socket-cli-config.mts' +import { SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH } from '../env/socket-cli-socket-patch-local-path.mts' import { SOCKET_CLI_DEBUG } from '../env/socket-cli-debug.mts' import { SOCKET_CLI_FIX } from '../env/socket-cli-fix.mts' import { SOCKET_CLI_GIT_USER_EMAIL } from '../env/socket-cli-git-user-email.mts' @@ -64,6 +65,7 @@ import { SOCKET_CLI_SEA_NODE_VERSION } from '../env/socket-cli-sea-node-version. import { SOCKET_CLI_SFW_LOCAL_PATH } from '../env/socket-cli-sfw-local-path.mts' import { SOCKET_CLI_SKIP_UPDATE_CHECK } from '../env/socket-cli-skip-update-check.mts' import { SOCKET_CLI_VIEW_ALL_RISKS } from '../env/socket-cli-view-all-risks.mts' +import { getSocketPatchVersion } from '../env/socket-patch-version.mts' import { getSynpVersion } from '../env/synp-version.mts' import { TEMP } from '../env/temp.mts' import { TERM } from '../env/term.mts' @@ -122,6 +124,7 @@ export { SOCKET_CLI_SEA_NODE_VERSION, SOCKET_CLI_SFW_LOCAL_PATH, SOCKET_CLI_SKIP_UPDATE_CHECK, + SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH, SOCKET_CLI_VIEW_ALL_RISKS, TEMP, TERM, @@ -143,6 +146,7 @@ export { getPyCliVersion, getPythonBuildTag, getPythonVersion, + getSocketPatchVersion, getSynpVersion, isPublishedBuild, isSentryBuild, @@ -202,6 +206,7 @@ const envSnapshot = { SOCKET_CLI_SEA_NODE_VERSION, SOCKET_CLI_SFW_LOCAL_PATH, SOCKET_CLI_SKIP_UPDATE_CHECK, + SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH, SOCKET_CLI_VIEW_ALL_RISKS, TEMP, TERM, @@ -222,6 +227,7 @@ const envSnapshot = { INLINED_SOCKET_CLI_PYCLI_VERSION: getPyCliVersion(), INLINED_SOCKET_CLI_SENTRY_BUILD: isSentryBuild(), INLINED_SOCKET_CLI_SFW_VERSION: getSwfVersion(), + INLINED_SOCKET_CLI_SOCKET_PATCH_VERSION: getSocketPatchVersion(), INLINED_SOCKET_CLI_SYNP_VERSION: getSynpVersion(), INLINED_SOCKET_CLI_VERSION: getCliVersion(), INLINED_SOCKET_CLI_VERSION_HASH: getCliVersionHash(), diff --git a/packages/cli/src/env/coana-version.mts b/packages/cli/src/env/coana-version.mts index a30735a4a..02b4acc64 100644 --- a/packages/cli/src/env/coana-version.mts +++ b/packages/cli/src/env/coana-version.mts @@ -9,5 +9,11 @@ import process from 'node:process' export function getCoanaVersion(): string { - return process.env['INLINED_SOCKET_CLI_COANA_VERSION'] ?? '' + const version = process.env['INLINED_SOCKET_CLI_COANA_VERSION'] + if (!version) { + throw new Error( + 'INLINED_SOCKET_CLI_COANA_VERSION not found. Please ensure @coana-tech/cli is properly configured in external-tools.json.', + ) + } + return version } diff --git a/packages/cli/src/env/sfw-version.mts b/packages/cli/src/env/sfw-version.mts index fd23071c1..81e50150a 100644 --- a/packages/cli/src/env/sfw-version.mts +++ b/packages/cli/src/env/sfw-version.mts @@ -9,5 +9,11 @@ import process from 'node:process' export function getSwfVersion(): string { - return process.env['INLINED_SOCKET_CLI_SFW_VERSION'] ?? '' + const version = process.env['INLINED_SOCKET_CLI_SFW_VERSION'] + if (!version) { + throw new Error( + 'INLINED_SOCKET_CLI_SFW_VERSION not found. Please ensure sfw is properly configured in external-tools.json.', + ) + } + return version } diff --git a/packages/cli/src/env/socket-cli-socket-patch-local-path.mts b/packages/cli/src/env/socket-cli-socket-patch-local-path.mts new file mode 100644 index 000000000..f42d71611 --- /dev/null +++ b/packages/cli/src/env/socket-cli-socket-patch-local-path.mts @@ -0,0 +1,14 @@ +import { envAsString } from '@socketsecurity/lib/env' + +/** + * Override the socket-patch path for local development/testing. + * When set, uses the local socket-patch CLI instead of downloading from npm. + * + * @example + * ```bash + * export SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH=/path/to/socket-patch/bin/cli.js + * ``` + */ +export const SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH = envAsString( + process.env['SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH'], +) diff --git a/packages/cli/src/env/socket-patch-version.mts b/packages/cli/src/env/socket-patch-version.mts new file mode 100644 index 000000000..e71879bf9 --- /dev/null +++ b/packages/cli/src/env/socket-patch-version.mts @@ -0,0 +1,19 @@ +/** + * Socket Patch version getter function. + * Uses direct process.env access so esbuild define can inline values. + * IMPORTANT: esbuild's define plugin can only replace direct process.env['KEY'] references. + * If we imported from env modules, esbuild couldn't inline the values at build time. + * This is critical for embedding version info into the binary. + */ + +import process from 'node:process' + +export function getSocketPatchVersion(): string { + const version = process.env['INLINED_SOCKET_CLI_SOCKET_PATCH_VERSION'] + if (!version) { + throw new Error( + 'INLINED_SOCKET_CLI_SOCKET_PATCH_VERSION not found. Please ensure socket-patch is properly configured in external-tools.json.', + ) + } + return version +} diff --git a/packages/cli/src/utils/fs/glob.mts b/packages/cli/src/utils/fs/glob.mts index efdd15139..cc619e3bc 100644 --- a/packages/cli/src/utils/fs/glob.mts +++ b/packages/cli/src/utils/fs/glob.mts @@ -165,6 +165,14 @@ export function filterBySupportedScanFiles( return filepaths.filter(p => micromatch.some(p, patterns, { dot: true })) } +export function createSupportedFilesFilter( + supportedFiles: SocketSdkSuccessResult<'getReportSupportedFiles'>['data'], +): (filepath: string) => boolean { + const patterns = getSupportedFilePatterns(supportedFiles) + return (filepath: string) => + micromatch.some(filepath, patterns, { dot: true }) +} + export function getSupportedFilePatterns( supportedFiles: SocketSdkSuccessResult<'getReportSupportedFiles'>['data'], ): string[] { @@ -179,6 +187,10 @@ export function getSupportedFilePatterns( } type GlobWithGitIgnoreOptions = GlobOptions & { + // Optional filter function to apply during streaming. + // When provided, only files passing this filter are accumulated. + // This is critical for memory efficiency when scanning large monorepos. + filter?: ((filepath: string) => boolean) | undefined socketConfig?: SocketYml | undefined } @@ -188,6 +200,7 @@ export async function globWithGitIgnore( ): Promise { const { cwd = process.cwd(), + filter, socketConfig, ...additionalOptions } = { __proto__: null, ...options } as GlobWithGitIgnoreOptions @@ -244,27 +257,39 @@ export async function globWithGitIgnore( ...additionalOptions, } as GlobOptions - if (!hasNegatedPattern) { + // When no filter is provided and no negated patterns exist, use the fast path. + if (!hasNegatedPattern && !filter) { return await fastGlob.glob(patterns as string[], globOptions) } - // Add support for negated "ignore" patterns which many globbing libraries, // including 'fast-glob', 'globby', and 'tinyglobby', lack support for. - const filtered: string[] = [] - const ig = ignore().add([...ignores]) + // Use streaming to avoid unbounded memory accumulation. + // This is critical for large monorepos with 100k+ files. + const results: string[] = [] + const ig = hasNegatedPattern ? ignore().add([...ignores]) : null const stream = fastGlob.globStream( patterns as string[], globOptions, ) as AsyncIterable for await (const p of stream) { - // Note: the input files must be INSIDE the cwd. If you get strange looking - // relative path errors here, most likely your path is outside the given cwd. - const relPath = globOptions.absolute ? path.relative(cwd, p) : p - if (!ig.ignores(relPath)) { - filtered.push(p) + // Check gitignore patterns with negation support. + if (ig) { + // Note: the input files must be INSIDE the cwd. If you get strange looking + // relative path errors here, most likely your path is outside the given cwd. + const relPath = globOptions.absolute ? path.relative(cwd, p) : p + if (ig.ignores(relPath)) { + continue + } + } + // Apply the optional filter to reduce memory usage. + // When scanning large monorepos, this filters early (e.g., to manifest files only) + // instead of accumulating all 100k+ files and filtering later. + if (filter && !filter(p)) { + continue } + results.push(p) } - return filtered + return results } export async function globWorkspace( diff --git a/packages/cli/src/utils/fs/path-resolve.mts b/packages/cli/src/utils/fs/path-resolve.mts index 1d73b296a..d3c5d3660 100644 --- a/packages/cli/src/utils/fs/path-resolve.mts +++ b/packages/cli/src/utils/fs/path-resolve.mts @@ -7,7 +7,7 @@ import { WIN32 } from '@socketsecurity/lib/constants/platform' import { isDirSync } from '@socketsecurity/lib/fs' import { - filterBySupportedScanFiles, + createSupportedFilesFilter, globWithGitIgnore, pathsToGlobPatterns, } from './glob.mts' @@ -127,13 +127,17 @@ export async function getPackageFilesForScan( ...options, } as PackageFilesForScanOptions - const filepaths = await globWithGitIgnore( + // Apply the supported files filter during streaming to avoid accumulating + // all files in memory. This is critical for large monorepos with 100k+ files + // where accumulating all paths before filtering causes OOM errors. + const filter = createSupportedFilesFilter(supportedFiles) + + return await globWithGitIgnore( pathsToGlobPatterns(inputPaths, options?.cwd), { cwd, + filter, socketConfig, }, ) - - return filterBySupportedScanFiles(filepaths!, supportedFiles) } diff --git a/packages/cli/src/utils/socket-patch/spawn.mts b/packages/cli/src/utils/socket-patch/spawn.mts new file mode 100644 index 000000000..ac5bbb04d --- /dev/null +++ b/packages/cli/src/utils/socket-patch/spawn.mts @@ -0,0 +1,126 @@ +/** @fileoverview Socket Patch CLI spawn utilities for Socket CLI. */ + +import { dlxPackage } from '@socketsecurity/lib/dlx/package' + +import { getDefaultOrgSlug } from '../../commands/ci/fetch-default-org-slug.mts' +import ENV from '../../constants/env.mts' +import { getErrorCause } from '../error/errors.mts' +import { getDefaultApiToken, getDefaultProxyUrl } from '../socket/sdk.mts' +import { spawnNode } from '../spawn/spawn-node.mjs' + +import type { ShadowBinOptions } from '../../shadow/npm-base.mjs' +import type { CResult } from '../../types.mjs' +import type { SpawnExtra } from '@socketsecurity/lib/spawn' + +export type SocketPatchSpawnOptions = ShadowBinOptions + +/** + * Helper to spawn socket-patch with package manager dlx commands. + * Returns a CResult with stdout extraction for backward compatibility. + * + * If SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH environment variable is set, uses the local + * Socket Patch CLI at that path instead of downloading from npm. + */ +export async function spawnSocketPatch( + args: string[] | readonly string[], + orgSlug?: string, + options?: SocketPatchSpawnOptions | undefined, + spawnExtra?: SpawnExtra | undefined, +): Promise> { + const { env: spawnEnv, ...shadowOptions } = { + __proto__: null, + ...options, + } as SocketPatchSpawnOptions + + const mixinsEnv: Record = { + SOCKET_CLI_VERSION: ENV.INLINED_SOCKET_CLI_VERSION || '', + } + const defaultApiToken = getDefaultApiToken() + if (defaultApiToken) { + mixinsEnv['SOCKET_CLI_API_TOKEN'] = defaultApiToken + } + + if (orgSlug) { + mixinsEnv['SOCKET_ORG_SLUG'] = orgSlug + } else { + const orgSlugCResult = await getDefaultOrgSlug() + if (orgSlugCResult.ok) { + mixinsEnv['SOCKET_ORG_SLUG'] = orgSlugCResult.data + } + } + + const proxyUrl = getDefaultProxyUrl() + if (proxyUrl) { + mixinsEnv['SOCKET_CLI_API_PROXY'] = proxyUrl + } + + // Forward SOCKET_PATCH_PROXY_URL if set. + if (ENV.SOCKET_PATCH_PROXY_URL) { + mixinsEnv['SOCKET_PATCH_PROXY_URL'] = ENV.SOCKET_PATCH_PROXY_URL + } + + try { + const localSocketPatchPath = ENV.SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH + // Use local Socket Patch CLI if path is provided. + if (localSocketPatchPath) { + const spawnResult = await spawnNode([localSocketPatchPath, ...args], { + ...shadowOptions, + env: { + ...process.env, + ...mixinsEnv, + ...spawnEnv, + }, + stdio: spawnExtra?.['stdio'] || 'inherit', + }) + + return { + ok: true, + data: spawnResult.stdout?.toString() ?? '', + } + } + + // Use dlx version. + const socketPatchVersion = ENV.INLINED_SOCKET_CLI_SOCKET_PATCH_VERSION + const packageSpec = `@socketsecurity/socket-patch@${socketPatchVersion}` + + const finalEnv = { + ...process.env, + ...mixinsEnv, + ...spawnEnv, + } + + const result = await dlxPackage( + args, + { + package: packageSpec, + force: true, + spawnOptions: { + cwd: + typeof shadowOptions.cwd === 'string' + ? shadowOptions.cwd + : shadowOptions.cwd?.toString(), + env: finalEnv as Record, + stdio: + (spawnExtra?.['stdio'] as 'inherit' | 'pipe' | undefined) || + 'inherit', + }, + }, + spawnExtra, + ) + + const output = await result.spawnPromise + return { + ok: true, + data: output.stdout?.toString() ?? '', + } + } catch (e) { + const stderr = (e as { stderr?: unknown })?.stderr + const cause = getErrorCause(e) + const message = stderr ? String(stderr) : cause + return { + ok: false, + data: e, + message, + } + } +}