diff --git a/CHANGELOG.md b/CHANGELOG.md index 3642eaf5e..7b624b8f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed - **Bazel diagnostics** — `socket manifest bazel --verbose` now emits bounded subprocess traces with argv, cwd, duration, exit status, output sizes, and failure stderr tails to make customer log-only triage safer and faster. +## [1.1.109](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.109) - 2026-05-28 + +### Added +- **`socket fix --exclude-paths`** — Skip matching paths from the scan entirely: manifests under these paths are not uploaded, and fixes are not applied to workspaces under them. Use this to skip directories the current user cannot read (e.g. a postgres `pgdata` directory inside the repo) so they do not abort manifest collection. The pre-existing `--exclude` flag keeps its previous fix-application-only semantic but is now hidden in `--help` in favor of `--exclude-paths`. + ## [1.1.108](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.108) - 2026-05-28 ### Changed diff --git a/package.json b/package.json index 36e647b0a..5f83f65df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socket", - "version": "1.1.108", + "version": "1.1.109", "description": "CLI for Socket.dev", "homepage": "https://github.com/SocketDev/socket-cli", "license": "MIT AND OFL-1.1", diff --git a/src/commands/fix/cmd-fix.integration.test.mts b/src/commands/fix/cmd-fix.integration.test.mts index 99c589f53..7ce327b44 100644 --- a/src/commands/fix/cmd-fix.integration.test.mts +++ b/src/commands/fix/cmd-fix.integration.test.mts @@ -169,7 +169,7 @@ describe('socket fix', async () => { --debug Enable debug logging in the Coana-based Socket Fix CLI invocation. --disable-external-tool-checks Disable external tool checks during fix analysis. --ecosystems Limit fix analysis to specific ecosystems. Accepts space- or comma-separated values and is case-insensitive. Defaults to all ecosystems. - --exclude Exclude workspaces matching these glob patterns. Can be provided as comma separated values or as multiple flags + --exclude-paths Skip matching paths from the scan entirely: manifests under these paths are not uploaded, and fixes are not applied to workspaces under them. Patterns are anchored micromatch globs matched relative to the target directory (CWD); \`data/postgres/pgdata\` matches that exact path, \`**/pgdata\` matches at any depth. Use this to skip directories the current user cannot read so they do not abort manifest collection. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags. --fix-version Override the version of @coana-tech/cli used for fix analysis. Default: . --id Provide a list of vulnerability identifiers to compute fixes for: - GHSA IDs (https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database#about-ghsa-ids) (e.g., GHSA-xxxx-xxxx-xxxx) diff --git a/src/commands/fix/cmd-fix.mts b/src/commands/fix/cmd-fix.mts index 75d51355a..4ff010c04 100644 --- a/src/commands/fix/cmd-fix.mts +++ b/src/commands/fix/cmd-fix.mts @@ -31,6 +31,7 @@ import { } from '../../utils/package-manager.mts' import { RangeStyles } from '../../utils/semver.mts' import { getDefaultOrgSlug } from '../ci/fetch-default-org-slug.mts' +import { assertValidExcludePaths } from '../scan/exclude-paths.mts' import type { MeowFlag, MeowFlags } from '../../flags.mts' import type { PURL_Type } from '../../utils/ecosystem.mts' @@ -82,6 +83,18 @@ const generalFlags: MeowFlags = { description: 'Exclude workspaces matching these glob patterns. Can be provided as comma separated values or as multiple flags', isMultiple: true, + // Hidden in favor of --exclude-paths, which covers both manifest + // discovery and workspace filtering. --exclude is preserved for + // backwards compatibility with the narrower (fix-application only) + // semantic. + hidden: true, + }, + excludePaths: { + type: 'string', + default: [], + description: + 'Skip matching paths from the scan entirely: manifests under these paths are not uploaded, and fixes are not applied to workspaces under them. Patterns are anchored micromatch globs matched relative to the target directory (CWD); `data/postgres/pgdata` matches that exact path, `**/pgdata` matches at any depth. Use this to skip directories the current user cannot read so they do not abort manifest collection. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.', + isMultiple: true, hidden: false, }, include: { @@ -314,6 +327,7 @@ async function run( disableExternalToolChecks, ecosystems, exclude, + excludePaths, fixVersion, include, json, @@ -339,6 +353,7 @@ async function run( disableExternalToolChecks: boolean ecosystems: string[] exclude: string[] + excludePaths: string[] fixVersion: string | undefined include: string[] json: boolean @@ -464,6 +479,19 @@ async function run( return } + const includePatterns = cmdFlagValueToArray(include) + const excludePatterns = cmdFlagValueToArray(exclude) + const excludePathsPatterns = cmdFlagValueToArray(excludePaths) + // Validate before the network round-trip so a bad pattern doesn't waste + // an org-slug API call. + try { + assertValidExcludePaths(excludePathsPatterns) + } catch (e) { + logger.fail((e as Error).message) + process.exitCode = 1 + return + } + if (dryRun) { logger.log(constants.DRY_RUN_NOT_SAVING) return @@ -482,9 +510,6 @@ async function run( const { spinner } = constants - const includePatterns = cmdFlagValueToArray(include) - const excludePatterns = cmdFlagValueToArray(exclude) - await handleFix({ all, applyFixes, @@ -496,6 +521,7 @@ async function run( disableMajorUpdates, ecosystems: validatedEcosystems, exclude: excludePatterns, + excludePaths: excludePathsPatterns, ghsas, include: includePatterns, minimumReleaseAge, diff --git a/src/commands/fix/coana-fix.mts b/src/commands/fix/coana-fix.mts index 7f9f2fdc8..61ed60731 100644 --- a/src/commands/fix/coana-fix.mts +++ b/src/commands/fix/coana-fix.mts @@ -47,6 +47,7 @@ import { } from '../../utils/github.mts' import { getPackageFilesForScan } from '../../utils/path-resolve.mts' import { setupSdk } from '../../utils/sdk.mts' +import { excludePathToScanIgnores } from '../scan/exclude-paths.mts' import { fetchSupportedScanFileNames } from '../scan/fetch-supported-scan-file-names.mts' import type { FixConfig } from './types.mts' @@ -129,6 +130,7 @@ export async function coanaFix( disableMajorUpdates, ecosystems, exclude, + excludePaths, ghsas, include, minimumReleaseAge, @@ -171,7 +173,21 @@ export async function coanaFix( ? socketYmlResult.data?.parsed : undefined + // Expand user-supplied `--exclude-paths` patterns into the fast-glob ignore + // form so manifest discovery skips them. Without this an unreadable + // subdirectory (e.g. a postgres `pgdata` owned by another uid) would crash + // `socket fix` before coana is even invoked. + const additionalIgnores = excludePaths.length + ? excludePaths.flatMap(excludePathToScanIgnores) + : undefined + // Forward --exclude-paths to coana's workspace filter too, so a workspace + // matching the pattern is also skipped during fix application even when + // its manifest somehow made it into the upload (e.g. picked up via a + // sibling manifest's references). --exclude stays separate as a hidden + // legacy escape hatch for the narrower "fix-application only" semantic. + const coanaExcludePatterns = [...exclude, ...excludePaths] const scanFilepaths = await getPackageFilesForScan(['.'], supportedFiles, { + additionalIgnores, config: socketConfig, cwd, }) @@ -289,7 +305,9 @@ export async function coanaFix( ? ['--minimum-release-age', minimumReleaseAge] : []), ...(include.length ? ['--include', ...include] : []), - ...(exclude.length ? ['--exclude', ...exclude] : []), + ...(coanaExcludePatterns.length + ? ['--exclude', ...coanaExcludePatterns] + : []), ...(ecosystems.length ? ['--purl-types', ...ecosystems] : []), ...(packageManagers.length ? ['--package-managers', ...packageManagers] @@ -451,7 +469,9 @@ export async function coanaFix( ? ['--minimum-release-age', minimumReleaseAge] : []), ...(include.length ? ['--include', ...include] : []), - ...(exclude.length ? ['--exclude', ...exclude] : []), + ...(coanaExcludePatterns.length + ? ['--exclude', ...coanaExcludePatterns] + : []), ...(ecosystems.length ? ['--purl-types', ...ecosystems] : []), ...(packageManagers.length ? ['--package-managers', ...packageManagers] diff --git a/src/commands/fix/handle-fix-limit.test.mts b/src/commands/fix/handle-fix-limit.test.mts index f861df735..8676cf301 100644 --- a/src/commands/fix/handle-fix-limit.test.mts +++ b/src/commands/fix/handle-fix-limit.test.mts @@ -80,6 +80,7 @@ describe('socket fix --pr-limit behavior verification', () => { disableMajorUpdates: false, ecosystems: [], exclude: [], + excludePaths: [], ghsas: [], include: [], minSatisfying: false, @@ -448,4 +449,63 @@ describe('socket fix --pr-limit behavior verification', () => { expect(ghsaArgs).toEqual(['GHSA-1111-1111-1111']) }) }) + + describe('--exclude-paths flag', () => { + it('passes excludePaths to getPackageFilesForScan as anchored ignore patterns', async () => { + mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' }) + + await coanaFix({ + ...baseConfig, + excludePaths: ['data/postgres/pgdata', '**/.cache'], + ghsas: ['GHSA-1111-1111-1111'], + }) + + expect(mockGetPackageFilesForScan).toHaveBeenCalledTimes(1) + const [, , opts] = mockGetPackageFilesForScan.mock.calls[0] ?? [] + // excludePathToScanIgnores emits both the entry itself and a /** sibling + // unless the user already passed a /** suffix. + expect(opts.additionalIgnores).toEqual([ + 'data/postgres/pgdata', + 'data/postgres/pgdata/**', + '**/.cache', + '**/.cache/**', + ]) + }) + + it('omits additionalIgnores when excludePaths is empty', async () => { + mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' }) + + await coanaFix({ + ...baseConfig, + excludePaths: [], + ghsas: ['GHSA-1111-1111-1111'], + }) + + expect(mockGetPackageFilesForScan).toHaveBeenCalledTimes(1) + const [, , opts] = mockGetPackageFilesForScan.mock.calls[0] ?? [] + expect(opts.additionalIgnores).toBeUndefined() + }) + + it('forwards excludePaths to coana --exclude alongside --exclude values', async () => { + mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' }) + + await coanaFix({ + ...baseConfig, + exclude: ['legacy-workspace'], + excludePaths: ['data/postgres/pgdata'], + ghsas: ['GHSA-1111-1111-1111'], + }) + + expect(mockSpawnCoanaDlx).toHaveBeenCalledTimes(1) + const callArgs = mockSpawnCoanaDlx.mock.calls[0]?.[0] as string[] + const excludeIndex = callArgs.indexOf('--exclude') + expect(excludeIndex).toBeGreaterThan(-1) + // --exclude is followed by every pattern from both sources, in order: + // legacy --exclude entries first, then --exclude-paths entries. + expect(callArgs.slice(excludeIndex + 1, excludeIndex + 3)).toEqual([ + 'legacy-workspace', + 'data/postgres/pgdata', + ]) + }) + }) }) diff --git a/src/commands/fix/handle-fix.mts b/src/commands/fix/handle-fix.mts index 5ceda8e0b..fb37fd5d9 100644 --- a/src/commands/fix/handle-fix.mts +++ b/src/commands/fix/handle-fix.mts @@ -124,6 +124,7 @@ export async function handleFix({ disableMajorUpdates, ecosystems, exclude, + excludePaths, ghsas, include, minSatisfying, @@ -152,6 +153,7 @@ export async function handleFix({ disableMajorUpdates, ecosystems, exclude, + excludePaths, ghsas, include, minSatisfying, @@ -179,6 +181,7 @@ export async function handleFix({ disableMajorUpdates, ecosystems, exclude, + excludePaths, // Convert mixed CVE/GHSA/PURL inputs to GHSA IDs only. ghsas: await convertIdsToGhsas(ghsas, { silence }), include, diff --git a/src/commands/fix/types.mts b/src/commands/fix/types.mts index fb73dd962..3a436a71f 100644 --- a/src/commands/fix/types.mts +++ b/src/commands/fix/types.mts @@ -13,6 +13,7 @@ export type FixConfig = { disableMajorUpdates: boolean ecosystems: PURL_Type[] exclude: string[] + excludePaths: string[] ghsas: string[] include: string[] minimumReleaseAge: string