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
2 changes: 1 addition & 1 deletion .config/rollup.dist.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ async function copyExternalPackages() {
[
[blessedPath, ['lib/**/*.js', 'usr/**/**', 'vendor/**/*.js', 'LICENSE*']],
[blessedContribPath, ['lib/**/*.js', 'index.js', 'LICENSE*']],
[coanaPath, ['**/*.mjs']],
[coanaPath, ['**/*.mjs', 'coana-repos/**/*']],
[
socketRegistryPath,
[
Expand Down
37 changes: 1 addition & 36 deletions src/commands/scan/cmd-scan-create.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'

import { handleCreateNewScan } from './handle-create-new-scan.mts'
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 constants from '../../constants.mts'
Expand Down Expand Up @@ -31,42 +32,6 @@ const {
SOCKET_DEFAULT_REPOSITORY,
} = constants

const reachabilityFlags: MeowFlags = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks odd to me since there's no usage removal but I guess these weren't used before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are in use. Just imported from reachability-flags.mts instead so they can be shared between scan create --reach and scan reach.

reachDisableAnalytics: {
type: 'boolean',
description:
'Disable reachability analytics sharing with Socket. Also disables caching-based optimizations.',
},
reachAnalysisMemoryLimit: {
type: 'number',
description:
'The maximum memory in MB to use for the reachability analysis. The default is 8192MB.',
default: 8192,
},
reachAnalysisTimeout: {
type: 'number',
description:
'Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly.',
},
reachEcosystems: {
type: 'string',
isMultiple: true,
description:
'List of ecosystems to conduct reachability analysis on, as either a comma separated value or as multiple flags. Defaults to all ecosystems.',
},
reachContinueOnFailingProjects: {
type: 'boolean',
description:
'Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace.',
},
reachExcludePaths: {
type: 'string',
isMultiple: true,
description:
'List of paths to exclude from reachability analysis, as either a comma separated value or as multiple flags.',
},
}

const config: CliCommandConfig = {
commandName: 'create',
description: 'Create a new Socket scan and report',
Expand Down
147 changes: 133 additions & 14 deletions src/commands/scan/cmd-scan-reach.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@ import path from 'node:path'
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 constants from '../../constants.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { type MeowFlags, commonFlags, outputFlags } from '../../flags.mts'
import { checkCommandInput } from '../../utils/check-input.mts'
import { cmdFlagValueToArray } from '../../utils/cmd.mts'
import { determineOrgSlug } from '../../utils/determine-org-slug.mts'
import {
type EcosystemString,
getEcosystemChoicesForMeow,
} from '../../utils/ecosystem.mts'
import { getOutputKind } from '../../utils/get-output-kind.mts'
import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
import { getFlagListOutput } from '../../utils/output-formatting.mts'
import { hasDefaultToken } from '../../utils/sdk.mts'

import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts'

Expand All @@ -21,18 +30,51 @@ const config: CliCommandConfig = {
flags: {
...commonFlags,
...outputFlags,
cwd: {
type: 'string',
description: 'working directory, defaults to process.cwd()',
},
org: {
type: 'string',
description:
'Force override the organization slug, overrides the default org from config',
},
...reachabilityFlags,
},
help: (command, config) => `
help: (command, config) => {
const allFlags = config.flags || {}
const generalFlags: MeowFlags = {}

// Separate general flags from reachability flags
for (const [key, value] of Object.entries(allFlags)) {
if (!reachabilityFlags[key]) {
generalFlags[key] = value
}
}

return `
Usage
$ ${command} [options] [CWD=.]

Options
${getFlagListOutput(config.flags)}
${getFlagListOutput(generalFlags)}

Reachability Options
${getFlagListOutput(reachabilityFlags)}

Runs the Socket reachability analysis without creating a scan in Socket.
The output is written to .socket.facts.json in the current working directory.

Note: Manifest files are uploaded to Socket's backend services because the
reachability analysis requires creating a Software Bill of Materials (SBOM)
from these files before the analysis can run.

Examples
$ ${command}
$ ${command} ./proj
`,
$ ${command} ./proj --reach-ecosystems npm,pypi
`
},
}

export const cmdScanReach = {
Expand All @@ -53,11 +95,85 @@ async function run(
parentName,
})

const { dryRun, json, markdown } = cli.flags
const {
cwd: cwdOverride,
dryRun = false,
interactive = true,
json,
markdown,
org: orgFlag,
reachAnalysisMemoryLimit,
reachAnalysisTimeout,
reachContinueOnFailingProjects,
reachDisableAnalytics,
} = cli.flags as {
cwd: string
dryRun: boolean
interactive: boolean
json: boolean
markdown: boolean
org: string
reachAnalysisTimeout?: number
reachAnalysisMemoryLimit?: number
reachContinueOnFailingProjects: boolean
reachDisableAnalytics: boolean
}

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

// Validate ecosystem values
const validEcosystems = getEcosystemChoicesForMeow()
const reachEcosystems: EcosystemString[] = []
for (const ecosystem of reachEcosystemsRaw) {
if (!validEcosystems.includes(ecosystem)) {
throw new Error(
`Invalid ecosystem: "${ecosystem}". Valid values are: ${validEcosystems.join(', ')}`,
)
}
reachEcosystems.push(ecosystem as EcosystemString)
}

const outputKind = getOutputKind(json, markdown)

const wasValidInput = checkCommandInput(outputKind)
const cwd =
cwdOverride && cwdOverride !== 'process.cwd()'
? path.resolve(process.cwd(), String(cwdOverride))
: process.cwd()

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

// Use suggestTarget if no targets specified and in interactive mode
if (!targets.length && !dryRun && interactive) {
targets = await suggestTarget()
}

Comment on lines +148 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the dryrun check happens after the checkCommandInput call below, usually the last step of these cmd- files.
This way all the tests can test input validation. As long as there is no network activity or write mutations, dry run can continue. Note that determineOrgSlug will pass on the dryRun state, for example, and skip network in that case.
Most of the time the cmd- files don't do write mutation or network access (or can do without in dryRun) up to the point where they call the handle file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good catch! I've moved the bail now.

// Determine org slug
const [orgSlug] = await determineOrgSlug(
String(orgFlag || ''),
interactive,
dryRun,
)

const hasApiToken = hasDefaultToken()

const wasValidInput = checkCommandInput(
outputKind,
{
nook: true,
test: !!orgSlug,
message: 'Org name by default setting, --org, or auto-discovered',
fail: 'missing',
},
{
nook: true,
test: hasApiToken,
message: 'This command requires an API token for access',
fail: 'missing (try `socket login`)',
},
)
if (!wasValidInput) {
return
}
Expand All @@ -67,16 +183,19 @@ async function run(
return
}

const { unknownFlags } = cli

let [cwd = '.'] = cli.input
// Note: path.resolve vs .join:
// If given path is absolute then cwd should not affect it.
cwd = path.resolve(process.cwd(), cwd)

await handleScanReach({
cwd,
orgSlug,
outputKind,
unknownFlags,
targets,
interactive,
reachabilityOptions: {
reachContinueOnFailingProjects: Boolean(reachContinueOnFailingProjects),
reachDisableAnalytics: Boolean(reachDisableAnalytics),
reachAnalysisTimeout: Number(reachAnalysisTimeout),
reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit),
reachEcosystems,
reachExcludePaths,
},
})
}
Loading
Loading