Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/commands/ci/handle-ci.mts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export async function handleCi(autoManifest: boolean): Promise<void> {
pendingHead: true,
pullRequest: 0,
reach: {
excludePaths: [],
reachAnalysisMemoryLimit: 0,
reachAnalysisTimeout: 0,
reachConcurrency: 1,
Expand Down
9 changes: 9 additions & 0 deletions src/commands/scan/cmd-scan-create.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'node:path'
import { joinAnd } from '@socketsecurity/registry/lib/arrays'
import { logger } from '@socketsecurity/registry/lib/logger'

import { assertNoNegationPatterns } from './exclude-paths.mts'
import { handleCreateNewScan } from './handle-create-new-scan.mts'
import { outputCreateNewScan } from './output-create-new-scan.mts'
import { reachabilityFlags } from './reachability-flags.mts'
Expand Down Expand Up @@ -279,6 +280,7 @@ async function run(
setAsAlertsPage: boolean
tmp: boolean
// Reachability flags.
excludePaths: string[] | undefined
reach: boolean
reachAnalysisMemoryLimit: number
reachAnalysisTimeout: number
Expand Down Expand Up @@ -463,9 +465,14 @@ async function run(
logger.error('')
}

const excludePaths = cmdFlagValueToArray(cli.flags['excludePaths'])
assertNoNegationPatterns(excludePaths)

const reachExcludePaths = cmdFlagValueToArray(cli.flags['reachExcludePaths'])

// Validation helpers for better readability.
const hasExcludePaths = excludePaths.length > 0

const hasReachEcosystems = reachEcosystems.length > 0

const hasReachExcludePaths = reachExcludePaths.length > 0
Expand All @@ -488,6 +495,7 @@ async function run(
reachVersion !== reachabilityFlags['reachVersion']?.default

const isUsingAnyReachabilityFlags =
hasExcludePaths ||
hasReachEcosystems ||
hasReachExcludePaths ||
isUsingNonDefaultAnalytics ||
Expand Down Expand Up @@ -608,6 +616,7 @@ async function run(
pendingHead: Boolean(pendingHead),
pullRequest: Number(pullRequest),
reach: {
excludePaths,
reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit),
reachAnalysisTimeout: Number(reachAnalysisTimeout),
reachConcurrency: Number(reachConcurrency),
Expand Down
59 changes: 59 additions & 0 deletions src/commands/scan/cmd-scan-create.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('socket scan create', async () => {
--workspace The workspace in the Socket Organization that the repository is in to associate with the full scan.

Reachability Options (when --reach is used)
--exclude-paths List of glob patterns to exclude from the entire Tier 1 scan, including SCA/SBOM manifest discovery. Patterns are matched relative to the project root. Bare directory names are auto-extended to recursive globs (e.g. `tests` becomes `tests/**`). Trailing slashes are stripped. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.
--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-concurrency Set the maximum number of concurrent reachability analysis runs. It is recommended to choose a concurrency level that ensures each analysis run has at least the --reach-analysis-memory-limit amount of memory available. NPM reachability analysis does not support concurrent execution, so the concurrency level is ignored for NPM.
Expand Down Expand Up @@ -185,6 +186,38 @@ describe('socket scan create', async () => {
},
)

cmdit(
[
'scan',
'create',
FLAG_ORG,
'fakeOrg',
'target',
FLAG_DRY_RUN,
'--repo',
'xyz',
'--branch',
'abc',
'--exclude-paths',
'tests',
FLAG_CONFIG,
'{"apiToken":"fakeToken"}',
],
'should fail when --exclude-paths is used without --reach',
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
const output = stdout + stderr
expect(output).toContain(
'Reachability analysis flags require --reach to be enabled',
)
expect(output).toContain('add --reach flag to use --reach-* options')
expect(
code,
'should exit with non-zero code when validation fails',
).not.toBe(0)
},
)

cmdit(
[
'scan',
Expand Down Expand Up @@ -437,6 +470,32 @@ describe('socket scan create', async () => {
},
)

cmdit(
[
'scan',
'create',
FLAG_ORG,
'fakeOrg',
'test/fixtures/commands/scan/simple-npm',
FLAG_DRY_RUN,
'--repo',
'xyz',
'--branch',
'abc',
'--reach',
'--exclude-paths',
'tests',
FLAG_CONFIG,
'{"apiToken":"fakeToken"}',
],
'should succeed when --exclude-paths is used with --reach',
async cmd => {
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
expect(code, 'should exit with code 0 when all flags are valid').toBe(0)
},
)

cmdit(
[
'scan',
Expand Down
4 changes: 4 additions & 0 deletions src/commands/scan/cmd-scan-reach.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'node:path'
import { joinAnd } from '@socketsecurity/registry/lib/arrays'
import { logger } from '@socketsecurity/registry/lib/logger'

import { assertNoNegationPatterns } from './exclude-paths.mts'
import { handleScanReach } from './handle-scan-reach.mts'
import { reachabilityFlags } from './reachability-flags.mts'
import { suggestTarget } from './suggest_target.mts'
Expand Down Expand Up @@ -167,8 +168,10 @@ async function run(
const dryRun = !!cli.flags['dryRun']

// Process comma-separated values for isMultiple flags.
const excludePaths = cmdFlagValueToArray(cli.flags['excludePaths'])
const reachEcosystemsRaw = cmdFlagValueToArray(cli.flags['reachEcosystems'])
const reachExcludePaths = cmdFlagValueToArray(cli.flags['reachExcludePaths'])
assertNoNegationPatterns(excludePaths)

// Validate ecosystem values.
const reachEcosystems: PURL_Type[] = []
Expand Down Expand Up @@ -272,6 +275,7 @@ async function run(
outputKind,
outputPath: outputPath || '',
reachabilityOptions: {
excludePaths,
reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit),
reachAnalysisTimeout: Number(reachAnalysisTimeout),
reachConcurrency: Number(reachConcurrency),
Expand Down
68 changes: 68 additions & 0 deletions src/commands/scan/cmd-scan-reach.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('socket scan reach', async () => {
--output Path to write the reachability report to (must end with .json). Defaults to .socket.facts.json in the current working directory.

Reachability Options
--exclude-paths List of glob patterns to exclude from the entire Tier 1 scan, including SCA/SBOM manifest discovery. Patterns are matched relative to the project root. Bare directory names are auto-extended to recursive globs (e.g. \`tests\` becomes \`tests/**\`). Trailing slashes are stripped. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags.
--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-concurrency Set the maximum number of concurrent reachability analysis runs. It is recommended to choose a concurrency level that ensures each analysis run has at least the --reach-analysis-memory-limit amount of memory available. NPM reachability analysis does not support concurrent execution, so the concurrency level is ignored for NPM.
Expand Down Expand Up @@ -295,6 +296,50 @@ describe('socket scan reach', async () => {
'scan',
'reach',
FLAG_DRY_RUN,
'--exclude-paths',
'node_modules,dist',
'--org',
'fakeOrg',
FLAG_CONFIG,
'{"apiToken":"fakeToken"}',
],
'should accept --exclude-paths with comma-separated values',
async cmd => {
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
expect(code, 'should exit with code 0').toBe(0)
},
)

cmdit(
[
'scan',
'reach',
FLAG_DRY_RUN,
'--exclude-paths',
'node_modules',
'--exclude-paths',
'dist',
'--org',
'fakeOrg',
FLAG_CONFIG,
'{"apiToken":"fakeToken"}',
],
'should accept multiple --exclude-paths flags',
async cmd => {
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
expect(code, 'should exit with code 0').toBe(0)
},
)

cmdit(
[
'scan',
'reach',
FLAG_DRY_RUN,
'--exclude-paths',
'build',
'--reach-exclude-paths',
'node_modules,dist',
'--org',
Expand All @@ -310,6 +355,29 @@ describe('socket scan reach', async () => {
},
)

cmdit(
[
'scan',
'reach',
FLAG_DRY_RUN,
'--exclude-paths',
'!tests/keep',
'--org',
'fakeOrg',
FLAG_CONFIG,
'{"apiToken":"fakeToken"}',
],
'should reject --exclude-paths negation patterns',
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
const output = stdout + stderr
expect(output).toContain(
"--exclude-paths does not support negation patterns. Got: '!tests/keep'.",
)
expect(code, 'should exit with non-zero code').not.toBe(0)
},
)

cmdit(
[
'scan',
Expand Down
1 change: 1 addition & 0 deletions src/commands/scan/create-scan-from-github.mts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ async function scanOneRepo(
pendingHead: true,
pullRequest: 0,
reach: {
excludePaths: [],
reachAnalysisMemoryLimit: 0,
reachAnalysisTimeout: 0,
reachConcurrency: 1,
Expand Down
25 changes: 25 additions & 0 deletions src/commands/scan/exclude-paths.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { InputError } from '../../utils/errors.mts'

export function excludePathToProjectIgnorePath(path: string): string {
const stripped = stripTrailingSlash(path)
return stripped.endsWith('/**') ? stripped : `${stripped}/**`
}

export function assertNoNegationPatterns(paths: readonly string[]): void {
for (const path of paths) {
if (path.startsWith('!')) {
throw new InputError(
`--exclude-paths does not support negation patterns. Got: '${path}'.`,
)
}
}
}

export function normalizeExcludePath(path: string): string {
const stripped = stripTrailingSlash(path)
return stripped.endsWith('/*') ? stripped : `${stripped}/**`
}

function stripTrailingSlash(path: string): string {
return path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path
}
49 changes: 49 additions & 0 deletions src/commands/scan/exclude-paths.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it } from 'vitest'

import {
assertNoNegationPatterns,
excludePathToProjectIgnorePath,
normalizeExcludePath,
} from './exclude-paths.mts'
import { InputError } from '../../utils/errors.mts'

describe('exclude-paths', () => {
describe('assertNoNegationPatterns', () => {
it('allows positive patterns', () => {
expect(() =>
assertNoNegationPatterns(['tests', 'packages/*']),
).not.toThrow()
})

it('rejects negation patterns', () => {
expect(() => assertNoNegationPatterns(['!tests/keep'])).toThrow(
InputError,
)
expect(() => assertNoNegationPatterns(['!tests/keep'])).toThrow(
"--exclude-paths does not support negation patterns. Got: '!tests/keep'.",
)
})
})

describe('excludePathToProjectIgnorePath', () => {
it.each([
['packages/*', 'packages/*/**'],
['tests', 'tests/**'],
['tests/', 'tests/**'],
['tests/**', 'tests/**'],
])('converts %s to %s', (input, expected) => {
expect(excludePathToProjectIgnorePath(input)).toBe(expected)
})
})

describe('normalizeExcludePath', () => {
it.each([
['tests', 'tests/**'],
['tests/', 'tests/**'],
['tests/*', 'tests/*'],
['tests/**', 'tests/**/**'],
])('normalizes %s to %s', (input, expected) => {
expect(normalizeExcludePath(input)).toBe(expected)
})
})
})
29 changes: 26 additions & 3 deletions src/commands/scan/handle-create-new-scan.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import { logger } from '@socketsecurity/registry/lib/logger'
import { pluralize } from '@socketsecurity/registry/lib/words'

import {
excludePathToProjectIgnorePath,
normalizeExcludePath,
} from './exclude-paths.mts'
import { fetchCreateOrgFullScan } from './fetch-create-org-full-scan.mts'
import { fetchSupportedScanFileNames } from './fetch-supported-scan-file-names.mts'
import { finalizeTier1Scan } from './finalize-tier1-scan.mts'
Expand Down Expand Up @@ -172,8 +176,27 @@
? socketYmlResult.data?.parsed
: undefined

const excludePaths = reach.runReachabilityAnalysis ? reach.excludePaths : []
const scaExcludeGlobs = excludePaths.map(excludePathToProjectIgnorePath)
const coanaExcludeGlobs = excludePaths.map(normalizeExcludePath)
const effectiveSocketConfig = scaExcludeGlobs.length
? {
...socketConfig,
projectIgnorePaths: [
...(socketConfig?.projectIgnorePaths ?? []),
...scaExcludeGlobs,
],
}
: socketConfig
const mergedReachabilityOptions = excludePaths.length
? {
...reach,
reachExcludePaths: [...reach.reachExcludePaths, ...coanaExcludeGlobs],
}
: reach

const packagePaths = await getPackageFilesForScan(targets, supportedFiles, {
config: socketConfig,
config: effectiveSocketConfig,

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / 🔍 Type Check

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / 🧪 Test Matrix (20, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / 🧪 Test Matrix (24, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / 🧪 Test Matrix (22, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / e2e-tests (20, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / e2e-tests (24, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / e2e-tests (22, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / 🔍 Type Check

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / 🧪 Test Matrix (24, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / 🧪 Test Matrix (20, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / 🧪 Test Matrix (22, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / e2e-tests (20, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / e2e-tests (22, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.

Check failure on line 199 in src/commands/scan/handle-create-new-scan.mts

View workflow job for this annotation

GitHub Actions / e2e-tests (24, ubuntu-latest)

Type '{ version?: 2; issueRules?: { [issueName: string]: boolean; }; githubApp?: SocketYmlGitHub; projectIgnorePaths: string[]; } | undefined' is not assignable to type 'SocketYml | undefined'.
cwd,
})

Expand Down Expand Up @@ -213,7 +236,7 @@
logger.error('')
logger.info('Starting reachability analysis...')
debugFn('notice', 'Reachability analysis enabled')
debugDir('inspect', { reachabilityOptions: reach })
debugDir('inspect', { reachabilityOptions: mergedReachabilityOptions })

spinner.start()

Expand All @@ -222,7 +245,7 @@
cwd,
orgSlug,
packagePaths,
reachabilityOptions: reach,
reachabilityOptions: mergedReachabilityOptions,
repoName,
spinner,
target: targets[0]!,
Expand Down
Loading
Loading