Skip to content
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ 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/).


## [1.1.34](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.33) - 2025-11-21

### Fixed
- The target path is now properly considered when conducting reachability analysis: `socket scan reach <target>` and `socket scan create --reach <target>`.
- Fixed a bug where manifest files `<target>` were not included in a scan when the target was pointing to a directory.

## [1.1.33](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.33) - 2025-11-20

### Changed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "socket",
"version": "1.1.33",
"version": "1.1.34",
"description": "CLI for Socket.dev",
"homepage": "https://github.com/SocketDev/socket-cli",
"license": "MIT AND OFL-1.1",
Expand Down
38 changes: 38 additions & 0 deletions src/commands/scan/cmd-scan-create.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { outputCreateNewScan } from './output-create-new-scan.mts'
import { reachabilityFlags } from './reachability-flags.mts'
import { suggestOrgSlug } from './suggest-org-slug.mts'
import { suggestTarget } from './suggest_target.mts'
import { validateReachabilityTarget } from './validate-reachability-target.mts'
import constants, { REQUIREMENTS_TXT, SOCKET_JSON } from '../../constants.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { checkCommandInput } from '../../utils/check-input.mts'
Expand Down Expand Up @@ -451,6 +452,16 @@ async function run(
reachSkipCache ||
reachDisableAnalysisSplitting

// Validate target constraints when --reach is enabled.
const reachTargetValidation = reach
? await validateReachabilityTarget(targets, cwd)
: {
isDirectory: false,
isInsideCwd: false,
isValid: true,
targetExists: false,
}

const wasValidInput = checkCommandInput(
outputKind,
{
Expand Down Expand Up @@ -494,6 +505,33 @@ async function run(
message: 'Reachability analysis flags require --reach to be enabled',
fail: 'add --reach flag to use --reach-* options',
},
{
nook: true,
test: !reach || reachTargetValidation.isValid,
message:
'Reachability analysis requires exactly one target directory when --reach is enabled',
fail: 'provide exactly one directory path',
},
{
nook: true,
test: !reach || reachTargetValidation.isDirectory,
message:
'Reachability analysis target must be a directory when --reach is enabled',
fail: 'provide a directory path, not a file',
},
{
nook: true,
test: !reach || reachTargetValidation.targetExists,
message: 'Target directory must exist when --reach is enabled',
fail: 'provide an existing directory path',
},
{
nook: true,
test: !reach || reachTargetValidation.isInsideCwd,
message:
'Target directory must be inside the current working directory when --reach is enabled',
fail: 'provide a path inside the working directory',
},
)
if (!wasValidInput) {
return
Expand Down
26 changes: 13 additions & 13 deletions src/commands/scan/cmd-scan-create.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down Expand Up @@ -369,7 +369,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down Expand Up @@ -404,7 +404,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down Expand Up @@ -433,7 +433,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down Expand Up @@ -526,7 +526,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down Expand Up @@ -594,7 +594,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand All @@ -620,7 +620,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand All @@ -646,7 +646,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down Expand Up @@ -676,7 +676,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down Expand Up @@ -709,7 +709,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand All @@ -734,7 +734,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand All @@ -759,7 +759,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down Expand Up @@ -789,7 +789,7 @@ describe('socket scan create', async () => {
'create',
FLAG_ORG,
'fakeOrg',
'target',
'test/fixtures/commands/scan/reach',
FLAG_DRY_RUN,
'--repo',
'xyz',
Expand Down
30 changes: 29 additions & 1 deletion src/commands/scan/cmd-scan-reach.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'
import { handleScanReach } from './handle-scan-reach.mts'
import { reachabilityFlags } from './reachability-flags.mts'
import { suggestTarget } from './suggest_target.mts'
import { validateReachabilityTarget } from './validate-reachability-target.mts'
import constants from '../../constants.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { checkCommandInput } from '../../utils/check-input.mts'
Expand Down Expand Up @@ -154,7 +155,7 @@ async function run(
: processCwd

// Accept zero or more paths. Default to cwd() if none given.
let targets = cli.input || [cwd]
let targets = cli.input.length ? cli.input : [cwd]

// Use suggestTarget if no targets specified and in interactive mode
if (!targets.length && !dryRun && interactive) {
Expand All @@ -167,6 +168,9 @@ async function run(

const outputKind = getOutputKind(json, markdown)

// Validate target constraints for reachability analysis.
const targetValidation = await validateReachabilityTarget(targets, cwd)

const wasValidInput = checkCommandInput(
outputKind,
{
Expand All @@ -187,6 +191,30 @@ async function run(
message: 'The json and markdown flags cannot be both set, pick one',
fail: 'omit one',
},
{
nook: true,
test: targetValidation.isValid,
message: 'Reachability analysis requires exactly one target directory',
fail: 'provide exactly one directory path',
},
{
nook: true,
test: targetValidation.isDirectory,
message: 'Reachability analysis target must be a directory',
fail: 'provide a directory path, not a file',
},
{
nook: true,
test: targetValidation.targetExists,
message: 'Target directory must exist',
fail: 'provide an existing directory path',
},
{
nook: true,
test: targetValidation.isInsideCwd,
message: 'Target directory must be inside the current working directory',
fail: 'provide a path inside the working directory',
},
)
if (!wasValidInput) {
return
Expand Down
2 changes: 1 addition & 1 deletion src/commands/scan/cmd-scan-reach.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ describe('socket scan reach', async () => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
const output = stdout + stderr
expect(output).toMatch(
/no eligible files|file.*dir.*must contain|not.*found/i,
/Target directory must exist|no eligible files|file.*dir.*must contain|not.*found/i,
)
expect(code).toBeGreaterThan(0)
},
Expand Down
1 change: 1 addition & 0 deletions src/commands/scan/handle-create-new-scan.mts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export async function handleCreateNewScan({
reachabilityOptions: reach,
repoName,
spinner,
target: targets[0]!,
})

spinner.stop()
Expand Down
1 change: 1 addition & 0 deletions src/commands/scan/handle-scan-reach.mts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export async function handleScanReach({
packagePaths,
reachabilityOptions,
spinner,
target: targets[0]!,
uploadManifests: true,
})

Expand Down
10 changes: 9 additions & 1 deletion src/commands/scan/perform-reachability-analysis.mts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type ReachabilityAnalysisOptions = {
reachabilityOptions: ReachabilityOptions
repoName?: string | undefined
spinner?: Spinner | undefined
target: string
uploadManifests?: boolean | undefined
}

Expand All @@ -51,9 +52,16 @@ export async function performReachabilityAnalysis(
reachabilityOptions,
repoName,
spinner,
target,
uploadManifests = true,
} = { __proto__: null, ...options } as ReachabilityAnalysisOptions

// Determine the analysis target - make it relative to cwd if absolute.
let analysisTarget = target
if (path.isAbsolute(analysisTarget)) {
analysisTarget = path.relative(cwd, analysisTarget) || '.'
}

// Check if user has enterprise plan for reachability analysis.
const orgsCResult = await fetchOrganization()
if (!orgsCResult.ok) {
Expand Down Expand Up @@ -136,7 +144,7 @@ export async function performReachabilityAnalysis(
// Build Coana arguments.
const coanaArgs = [
'run',
cwd,
analysisTarget,
'--output-dir',
cwd,
'--socket-mode',
Expand Down
53 changes: 53 additions & 0 deletions src/commands/scan/validate-reachability-target.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { existsSync, promises as fs } from 'node:fs'
import path from 'node:path'

export type ReachabilityTargetValidation = {
isDirectory: boolean
isInsideCwd: boolean
isValid: boolean
targetExists: boolean
}

/**
* Validates that a target directory meets the requirements for reachability analysis.
*
* @param targets - Array of target paths to validate.
* @param cwd - Current working directory.
* @returns Validation result object with boolean flags.
*/
export async function validateReachabilityTarget(
targets: string[],
cwd: string,
): Promise<ReachabilityTargetValidation> {
const result: ReachabilityTargetValidation = {
isDirectory: false,
isInsideCwd: false,
isValid: targets.length === 1,
targetExists: false,
}

if (!result.isValid || !targets[0]) {
return result
}

// Resolve cwd to absolute path to handle relative cwd values.
const absoluteCwd = path.resolve(cwd)

// Resolve target path to absolute for validation.
const targetPath = path.isAbsolute(targets[0])
? targets[0]
: path.resolve(absoluteCwd, targets[0])

// Check if target is inside cwd.
const relativePath = path.relative(absoluteCwd, targetPath)
result.isInsideCwd =
!relativePath.startsWith('..') && !path.isAbsolute(relativePath)

result.targetExists = existsSync(targetPath)
if (result.targetExists) {
const targetStat = await fs.stat(targetPath)
result.isDirectory = targetStat.isDirectory()
}

return result
}
16 changes: 14 additions & 2 deletions src/utils/glob.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ignore from 'ignore'
import micromatch from 'micromatch'
import { parse as yamlParse } from 'yaml'

import { safeReadFile } from '@socketsecurity/registry/lib/fs'
import { isDirSync, safeReadFile } from '@socketsecurity/registry/lib/fs'
import { defaultIgnore } from '@socketsecurity/registry/lib/globs'
import { readPackageJson } from '@socketsecurity/registry/lib/packages'
import { transform } from '@socketsecurity/registry/lib/streams'
Expand Down Expand Up @@ -289,7 +289,19 @@ export function isReportSupportedFile(

export function pathsToGlobPatterns(
paths: string[] | readonly string[],
cwd?: string | undefined,
): string[] {
// TODO: Does not support `~/` paths.
return paths.map(p => (p === '.' || p === './' ? '**/*' : p))
return paths.map(p => {
// Convert current directory references to glob patterns.
if (p === '.' || p === './') {
return '**/*'
}
const absolutePath = path.isAbsolute(p) ? p : path.resolve(cwd ?? process.cwd(), p)
// If the path is a directory, scan it recursively for all files.
if (isDirSync(absolutePath)) {
return `${p}/**/*`
}
return p
})
}
Loading