From f465482f9a4417f0f21c340b1ca8e960e5c39687 Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Thu, 28 May 2026 15:53:31 +0200 Subject: [PATCH 1/3] fix: add --exclude-paths to socket fix manifest discovery socket fix walks the project tree to collect manifest files before uploading them to the API. That walk runs through fast-glob which throws on the first unreadable subdirectory: EACCES: permission denied, scandir '/home/user/proj/data/postgres/pgdata' A postgres data dir owned by another uid, an unreadable Docker volume mount, or any other directory the running user can't enter would crash 'socket fix' before coana is even invoked. The existing --exclude flag is forwarded to coana for workspace-level filtering but never threaded into the manifest discovery walk, so users had no way to skip problem directories. Mirror the --exclude-paths shape already used by 'socket scan create' (same validation, same anchored micromatch semantics, same ignore expansion) and pass it to getPackageFilesForScan as additionalIgnores. --- src/commands/fix/cmd-fix.integration.test.mts | 1 + src/commands/fix/cmd-fix.mts | 28 ++++++++++++-- src/commands/fix/coana-fix.mts | 10 +++++ src/commands/fix/handle-fix-limit.test.mts | 38 +++++++++++++++++++ src/commands/fix/handle-fix.mts | 3 ++ src/commands/fix/types.mts | 1 + 6 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/commands/fix/cmd-fix.integration.test.mts b/src/commands/fix/cmd-fix.integration.test.mts index 99c589f53..166d26707 100644 --- a/src/commands/fix/cmd-fix.integration.test.mts +++ b/src/commands/fix/cmd-fix.integration.test.mts @@ -170,6 +170,7 @@ describe('socket fix', async () => { --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 List of glob patterns to exclude from manifest discovery. Patterns are anchored micromatch globs matched relative to the target directory (CWD); \`data/postgres/pgdata\` matches only at that exact path, \`**/pgdata\` matches at any depth. Use this to skip directories the current user cannot read (e.g. a postgres data dir owned by another user) 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..ebcf6ba6d 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' @@ -84,6 +85,14 @@ const generalFlags: MeowFlags = { isMultiple: true, hidden: false, }, + excludePaths: { + type: 'string', + default: [], + description: + 'List of glob patterns to exclude from manifest discovery. Patterns are anchored micromatch globs matched relative to the target directory (CWD); `data/postgres/pgdata` matches only at that exact path, `**/pgdata` matches at any depth. Use this to skip directories the current user cannot read (e.g. a postgres data dir owned by another user) 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: { type: 'string', default: [], @@ -314,6 +323,7 @@ async function run( disableExternalToolChecks, ecosystems, exclude, + excludePaths, fixVersion, include, json, @@ -339,6 +349,7 @@ async function run( disableExternalToolChecks: boolean ecosystems: string[] exclude: string[] + excludePaths: string[] fixVersion: string | undefined include: string[] json: boolean @@ -464,6 +475,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 +506,6 @@ async function run( const { spinner } = constants - const includePatterns = cmdFlagValueToArray(include) - const excludePatterns = cmdFlagValueToArray(exclude) - await handleFix({ all, applyFixes, @@ -496,6 +517,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..8d4a0118b 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,15 @@ 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 const scanFilepaths = await getPackageFilesForScan(['.'], supportedFiles, { + additionalIgnores, config: socketConfig, cwd, }) diff --git a/src/commands/fix/handle-fix-limit.test.mts b/src/commands/fix/handle-fix-limit.test.mts index f861df735..b4ce4b4aa 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,41 @@ 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() + }) + }) }) 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 From 019c1c9474c0b59ee7e9dd86b8180aeb51b63cd3 Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Thu, 28 May 2026 16:34:18 +0200 Subject: [PATCH 2/3] fix: --exclude-paths covers both manifest discovery and workspace fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make --exclude-paths the canonical path-exclusion flag on socket fix: its value is now also forwarded to coana's --exclude alongside the existing --exclude entries, so a matched path is skipped consistently across manifest upload and fix application. Hide the legacy --exclude flag in --help. Its narrower fix-application only semantic is preserved unchanged so existing scripts that rely on 'detect vulnerabilities everywhere, only write fixes outside the excluded workspace' continue to work — but new users land on --exclude-paths. --- CHANGELOG.md | 1 + src/commands/fix/cmd-fix.integration.test.mts | 3 +-- src/commands/fix/cmd-fix.mts | 8 +++++-- src/commands/fix/coana-fix.mts | 14 ++++++++++-- src/commands/fix/handle-fix-limit.test.mts | 22 +++++++++++++++++++ 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ec626b3..e23f6eab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ 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] +- **`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`. - **`socket manifest bazel [beta]`** — Generate Bazel JVM SBOM manifests by running `bazel query` against discovered Maven repos in a Bazel workspace. Closes the inline-Maven-declaration gap that lockfile-only parsing misses for repos like envoy, ray, tensorflow, tink-java, and or-tools. Auto-detects Bzlmod and legacy `WORKSPACE`. - **`socket scan create --auto-manifest`** now covers Bazel workspaces in addition to Gradle/Scala/Kotlin/Conda. Repos with `MODULE.bazel`, `WORKSPACE`, or `WORKSPACE.bazel` are detected automatically and their Maven dependencies extracted as part of the standard scan-create flow. - **Bazel PyPI extraction** — `socket manifest bazel --ecosystem pypi` now generates `requirements.txt` for Python Bazel workspaces. Discovers custom `rules_python` pip hub names with Bazel command output first, queries `py_library` / `py_binary` / `py_test` dependencies, resolves canonical pinned versions from `requirements_lock.txt`, and emits PEP 503-normalized `name==version` lines. Supports both Bzlmod (`pip.parse`) and legacy `WORKSPACE` (`pip_parse` / `pip_install`) configurations. PyPI remains explicit opt-in for `socket scan create --auto-manifest` until real-world no-lockfile recovery is validated. diff --git a/src/commands/fix/cmd-fix.integration.test.mts b/src/commands/fix/cmd-fix.integration.test.mts index 166d26707..7ce327b44 100644 --- a/src/commands/fix/cmd-fix.integration.test.mts +++ b/src/commands/fix/cmd-fix.integration.test.mts @@ -169,8 +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 List of glob patterns to exclude from manifest discovery. Patterns are anchored micromatch globs matched relative to the target directory (CWD); \`data/postgres/pgdata\` matches only at that exact path, \`**/pgdata\` matches at any depth. Use this to skip directories the current user cannot read (e.g. a postgres data dir owned by another user) so they do not abort manifest collection. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or 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 ebcf6ba6d..4ff010c04 100644 --- a/src/commands/fix/cmd-fix.mts +++ b/src/commands/fix/cmd-fix.mts @@ -83,13 +83,17 @@ const generalFlags: MeowFlags = { description: 'Exclude workspaces matching these glob patterns. Can be provided as comma separated values or as multiple flags', isMultiple: true, - hidden: false, + // 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: - 'List of glob patterns to exclude from manifest discovery. Patterns are anchored micromatch globs matched relative to the target directory (CWD); `data/postgres/pgdata` matches only at that exact path, `**/pgdata` matches at any depth. Use this to skip directories the current user cannot read (e.g. a postgres data dir owned by another user) so they do not abort manifest collection. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.', + '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, }, diff --git a/src/commands/fix/coana-fix.mts b/src/commands/fix/coana-fix.mts index 8d4a0118b..61ed60731 100644 --- a/src/commands/fix/coana-fix.mts +++ b/src/commands/fix/coana-fix.mts @@ -180,6 +180,12 @@ export async function coanaFix( 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, @@ -299,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] @@ -461,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 b4ce4b4aa..8676cf301 100644 --- a/src/commands/fix/handle-fix-limit.test.mts +++ b/src/commands/fix/handle-fix-limit.test.mts @@ -485,5 +485,27 @@ describe('socket fix --pr-limit behavior verification', () => { 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', + ]) + }) }) }) From 06dc57fe670394419ef5719ff1c450ebf5d4e576 Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Thu, 28 May 2026 16:39:58 +0200 Subject: [PATCH 3/3] chore(release): cut 1.1.109 with --exclude-paths flag --- CHANGELOG.md | 6 +++++- package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04bcf5caf..7b624b8f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,6 @@ 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] -- **`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`. - **`socket manifest bazel [beta]`** — Generate Bazel JVM SBOM manifests by running `bazel query` against discovered Maven repos in a Bazel workspace. Closes the inline-Maven-declaration gap that lockfile-only parsing misses for repos like envoy, ray, tensorflow, tink-java, and or-tools. Auto-detects Bzlmod and legacy `WORKSPACE`. - **`socket scan create --auto-manifest`** now covers Bazel workspaces in addition to Gradle/Scala/Kotlin/Conda. Repos with `MODULE.bazel`, `WORKSPACE`, or `WORKSPACE.bazel` are detected automatically and their Maven dependencies extracted as part of the standard scan-create flow. - **Bazel PyPI extraction** — `socket manifest bazel --ecosystem pypi` now generates `requirements.txt` for Python Bazel workspaces. Discovers custom `rules_python` pip hub names with Bazel command output first, queries `py_library` / `py_binary` / `py_test` dependencies, resolves canonical pinned versions from `requirements_lock.txt`, and emits PEP 503-normalized `name==version` lines. Supports both Bzlmod (`pip.parse`) and legacy `WORKSPACE` (`pip_parse` / `pip_install`) configurations. PyPI remains explicit opt-in for `socket scan create --auto-manifest` until real-world no-lockfile recovery is validated. @@ -13,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",