Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
32ebd1c
Add socket manifest dynamic-sbom-inference
jfblaa Jul 30, 2026
ed803ad
Fix misleading --exclude-paths description on manifest commands
jfblaa Jul 31, 2026
7072d24
Add per-ecosystem javaHome config for gradle/maven/sbt manifest gener…
jfblaa Jul 31, 2026
3f5bdef
Resolve socket.json per build root, and support env var refs in javaHome
jfblaa Jul 31, 2026
836d9fb
Deep-merge the socket.json cascade instead of nearest-wins
jfblaa Jul 31, 2026
20e6206
Warn (but proceed) when a resolved config sets facts: false
jfblaa Jul 31, 2026
09a585e
Revert facts:false to skip, add a dedicated ignored config
jfblaa Jul 31, 2026
4116c11
Consolidate ignored into disabled instead of a separate field
jfblaa Jul 31, 2026
1ec6566
Add explicit clear sentinel, lightweight workspace discovery, and rec…
jfblaa Jul 31, 2026
b8e20bb
Write --exclude-paths disable at the exclusion root, not per project
jfblaa Aug 2, 2026
9c16b41
Abort recursive facts generation when a build root's workspace layout…
jfblaa Aug 2, 2026
3c8758b
Make recursive setup actually configure discovered build roots
jfblaa Aug 3, 2026
565b284
Fix null-clear and exclude-paths bugs in the recursive setup wizard
jfblaa Aug 3, 2026
36f354f
Trim verbose comments across the dynamic-sbom-inference feature
jfblaa Aug 3, 2026
58fa321
Address code review findings on the dynamic-sbom-inference PR
jfblaa Aug 3, 2026
ff34540
Merge remote-tracking branch 'origin/v1.x' into jfblaa/rea-685-add-so…
jfblaa Aug 3, 2026
23660e7
Refresh CLI banner snapshots after merging v1.x's version bump
jfblaa Aug 3, 2026
4359368
Use 1.1.153-prerelease pending the stuck 1.1.152 release
jfblaa Aug 3, 2026
4a41695
Simplify dynamic-sbom-inference's summary output
jfblaa Aug 3, 2026
53502d0
Reduce the summary line to just the generated count
jfblaa Aug 3, 2026
0ce3863
Trim verbose comment in output-manifest-dynamic-sbom-inference
jfblaa Aug 3, 2026
ff715ef
Make the CLI-banner-version test normalization suffix-aware
jfblaa Aug 3, 2026
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
8 changes: 8 additions & 0 deletions .config/rollup.dist.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ async function copyManifestScripts() {
path.join(srcDir, 'socket-facts.plugin.scala'),
path.join(destDir, 'socket-facts.plugin.scala'),
),
fs.copyFile(
path.join(srcDir, 'socket-workspaces.init.gradle'),
path.join(destDir, 'socket-workspaces.init.gradle'),
),
fs.copyFile(
path.join(srcDir, 'socket-workspaces.plugin.scala'),
path.join(destDir, 'socket-workspaces.plugin.scala'),
),
])
const jarPath = path.join(
srcDir,
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.152",
"version": "1.1.153-prerelease",
"description": "CLI for Socket.dev",
"contentPolicy": {
"class": "dual-use"
Expand Down
2 changes: 1 addition & 1 deletion src/commands/manifest/cmd-manifest-auto.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'

import { detectManifestActions } from './detect-manifest-actions.mts'
import { generateAutoManifest } from './generate_auto_manifest.mts'
import { excludePathsFlag } from './manifest-flags.mts'
import constants from '../../constants.mts'
import { commonFlags } from '../../flags.mts'
import { cmdFlagValueToArray } from '../../utils/cmd.mts'
Expand All @@ -14,7 +15,6 @@ import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
import { getFlagListOutput } from '../../utils/output-formatting.mts'
import { readOrDefaultSocketJson } from '../../utils/socket-json.mts'
import { assertValidExcludePaths } from '../scan/exclude-paths.mts'
import { excludePathsFlag } from '../scan/reachability-flags.mts'

import type {
CliCommandConfig,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/manifest/cmd-manifest-auto.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('socket manifest auto', async () => {
$ socket manifest auto [options] [CWD=.]

Options
--exclude-paths List of glob patterns to exclude from the scan, including SCA/SBOM manifest discovery and (when --reach is enabled) full application reachability analysis. Patterns are anchored micromatch globs matched relative to the Socket scan root, which is the command working directory (\`--cwd\` if set), not the reachability target: \`tests\` matches only \`<cwd>/tests\`; use \`**/tests\` to match at any depth. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags.
--exclude-paths List of glob patterns to exclude from manifest/facts generation. Patterns are anchored micromatch globs matched relative to CWD (\`--cwd\` if set): \`tests\` matches only \`<cwd>/tests\`; use \`**/tests\` to match at any depth. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags.
--verbose Enable debug output (only for auto itself; sub-steps need to have it pre-configured), may help when running into errors

Tries to figure out what language your target repo uses. If it finds a
Expand Down
134 changes: 134 additions & 0 deletions src/commands/manifest/cmd-manifest-dynamic-sbom-inference.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import path from 'node:path'

import { logger } from '@socketsecurity/registry/lib/logger'

import { handleManifestDynamicSbomInference } from './handle-manifest-dynamic-sbom-inference.mts'
import { excludePathsFlag } from './manifest-flags.mts'
import constants, { FLAG_JSON, FLAG_MARKDOWN } from '../../constants.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { checkCommandInput } from '../../utils/check-input.mts'
import { cmdFlagValueToArray } from '../../utils/cmd.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 { assertValidExcludePaths } from '../scan/exclude-paths.mts'

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

const config: CliCommandConfig = {
commandName: 'dynamic-sbom-inference',
description:
'Recursively discover gradle/sbt/maven build roots and generate a Socket facts SBOM for each',
hidden: false,
flags: {
...commonFlags,
...outputFlags,
...excludePathsFlag,
verbose: {
type: 'boolean',
default: false,
description: 'Print debug messages',
},
},
help: (command, config) => `
Usage
$ ${command} [options] [CWD=.]

Recursively walks CWD, discovers independent gradle, sbt, and maven build
roots, and generates a Socket facts SBOM (.socket.facts.json) for each,
skipping subproject/reactor-module directories a parent build root already
covers. Unlike \`socket manifest auto\`, this looks beyond CWD itself.

Options
${getFlagListOutput(config.flags)}

Examples

$ ${command}
$ ${command} ./monorepo
`,
}

export const cmdManifestDynamicSbomInference = {
description: config.description,
hidden: config.hidden,
run,
}

async function run(
argv: string[] | readonly string[],
importMeta: ImportMeta,
{ parentName }: CliCommandContext,
): Promise<void> {
const cli = meowOrExit({
argv,
config,
importMeta,
parentName,
})

const {
dryRun,
json,
markdown,
verbose: verboseFlag,
} = cli.flags as {
dryRun: boolean
json: boolean
markdown: boolean
verbose: boolean | undefined
}
const verbose = !!verboseFlag

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)

if (verbose) {
logger.group('- ', parentName, config.commandName, ':')
logger.group('- flags:', cli.flags)
logger.groupEnd()
logger.log('- target:', cwd)
logger.groupEnd()
}

const outputKind = getOutputKind(json, markdown)

const wasValidInput = checkCommandInput(
outputKind,
{
nook: true,
test: cli.input.length <= 1,
message: 'Can only accept one DIR (make sure to escape spaces!)',
fail: `received ${cli.input.length}`,
},
{
nook: true,
test: !json || !markdown,
message: `The \`${FLAG_JSON}\` and \`${FLAG_MARKDOWN}\` flags can not be used at the same time`,
fail: 'bad',
},
)
if (!wasValidInput) {
return
}

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

if (dryRun) {
logger.log(constants.DRY_RUN_BAILING_NOW)
return
}

await handleManifestDynamicSbomInference({
cwd,
excludePaths,
outputKind,
verbose,
})
}
76 changes: 76 additions & 0 deletions src/commands/manifest/cmd-manifest-dynamic-sbom-inference.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, expect } from 'vitest'

import constants, {
FLAG_CONFIG,
FLAG_DRY_RUN,
FLAG_HELP,
} from '../../../src/constants.mts'
import { cmdit, spawnSocketCli, testPath } from '../../../test/utils.mts'

describe('socket manifest dynamic-sbom-inference', async () => {
const { binCliPath } = constants

cmdit(
['manifest', 'dynamic-sbom-inference', FLAG_HELP, FLAG_CONFIG, '{}'],
`should support ${FLAG_HELP}`,
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd, {
cwd: testPath,
})
expect(stdout).toMatchInlineSnapshot(`
"Recursively discover gradle/sbt/maven build roots and generate a Socket facts SBOM for each

Usage
$ socket manifest dynamic-sbom-inference [options] [CWD=.]

Recursively walks CWD, discovers independent gradle, sbt, and maven build
roots, and generates a Socket facts SBOM (.socket.facts.json) for each,
skipping subproject/reactor-module directories a parent build root already
covers. Unlike \`socket manifest auto\`, this looks beyond CWD itself.

Options
--exclude-paths List of glob patterns to exclude from manifest/facts generation. Patterns are anchored micromatch globs matched relative to CWD (\`--cwd\` if set): \`tests\` matches only \`<cwd>/tests\`; use \`**/tests\` to match at any depth. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags.
--json Output as JSON
--markdown Output as Markdown
--verbose Print debug messages

Examples

$ socket manifest dynamic-sbom-inference
$ socket manifest dynamic-sbom-inference ./monorepo"
`)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | CLI: <redacted>
|__ | * | _| '_| -_| _| | token: <redacted>, org: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest dynamic-sbom-inference\`, cwd: <redacted>"
`)

expect(code, 'explicit help should exit with code 0').toBe(0)
expect(stderr, 'banner includes base command').toContain(
'`socket manifest dynamic-sbom-inference`',
)
},
)

cmdit(
['manifest', 'dynamic-sbom-inference', FLAG_DRY_RUN, FLAG_CONFIG, '{}'],
'should exit with dry-run message before touching disk',
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd, {
cwd: testPath,
})
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | CLI: <redacted>
|__ | * | _| '_| -_| _| | token: <redacted>, org: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest dynamic-sbom-inference\`, cwd: <redacted>"
`)

expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
},
)
})
14 changes: 9 additions & 5 deletions src/commands/manifest/cmd-manifest-gradle.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'

import { convertGradleToFacts } from './convert-gradle-to-facts.mts'
import { convertGradleToMaven } from './convert_gradle_to_maven.mts'
import { excludePathsFlag } from './manifest-flags.mts'
import { parseBuildToolOpts } from './parse-build-tool-opts.mts'
import { resolveBuildToolBin } from './scripts/build-tool.mts'
import constants, { REQUIREMENTS_TXT, SOCKET_JSON } from '../../constants.mts'
Expand All @@ -16,7 +17,6 @@ import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
import { getFlagListOutput } from '../../utils/output-formatting.mts'
import { readOrDefaultSocketJson } from '../../utils/socket-json.mts'
import { assertValidExcludePaths } from '../scan/exclude-paths.mts'
import { excludePathsFlag } from '../scan/reachability-flags.mts'

import type {
CliCommandConfig,
Expand Down Expand Up @@ -206,8 +206,8 @@ async function run(
}
}
if (includeConfigs === undefined) {
if (sockJson.defaults?.manifest?.gradle?.includeConfigs !== undefined) {
includeConfigs = sockJson.defaults?.manifest?.gradle?.includeConfigs
if (sockJson.defaults?.manifest?.gradle?.includeConfigs) {
includeConfigs = sockJson.defaults.manifest.gradle.includeConfigs
logger.info(
`Using default --include-configs from ${SOCKET_JSON}:`,
includeConfigs,
Expand All @@ -217,8 +217,8 @@ async function run(
}
}
if (excludeConfigs === undefined) {
if (sockJson.defaults?.manifest?.gradle?.excludeConfigs !== undefined) {
excludeConfigs = sockJson.defaults?.manifest?.gradle?.excludeConfigs
if (sockJson.defaults?.manifest?.gradle?.excludeConfigs) {
excludeConfigs = sockJson.defaults.manifest.gradle.excludeConfigs
logger.info(
`Using default --exclude-configs from ${SOCKET_JSON}:`,
excludeConfigs,
Expand Down Expand Up @@ -276,10 +276,13 @@ async function run(
return
}

const javaHome = sockJson.defaults?.manifest?.gradle?.javaHome ?? undefined

if (verbose) {
logger.group()
logger.info('- cwd:', cwd)
logger.info('- gradle bin:', bin)
logger.info('- java home:', javaHome || '(inherited)')
logger.groupEnd()
}

Expand All @@ -302,6 +305,7 @@ async function run(
gradleOpts: parsedGradleOpts,
ignoreUnresolved: Boolean(ignoreUnresolved),
includeConfigs: String(includeConfigs || ''),
javaHome,
verbose: Boolean(verbose),
})
return
Expand Down
2 changes: 1 addition & 1 deletion src/commands/manifest/cmd-manifest-gradle.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('socket manifest gradle', async () => {
Options
--bin Location of the gradle binary to use, default: ./gradlew if present, else gradle on PATH
--exclude-configs When generating facts: comma-separated glob patterns; Gradle configurations matching any pattern are skipped (applied after --include-configs)
--exclude-paths List of glob patterns to exclude from the scan, including SCA/SBOM manifest discovery and (when --reach is enabled) full application reachability analysis. Patterns are anchored micromatch globs matched relative to the Socket scan root, which is the command working directory (\`--cwd\` if set), not the reachability target: \`tests\` matches only \`<cwd>/tests\`; use \`**/tests\` to match at any depth. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags.
--exclude-paths List of glob patterns to exclude from manifest/facts generation. Patterns are anchored micromatch globs matched relative to CWD (\`--cwd\` if set): \`tests\` matches only \`<cwd>/tests\`; use \`**/tests\` to match at any depth. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags.
--facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph. This is the default; pass \`--pom\` to generate \`pom.xml\` files instead
--gradle-opts Additional options to pass on to ./gradlew, see \`./gradlew --help\`
--ignore-unresolved When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)
Expand Down
14 changes: 9 additions & 5 deletions src/commands/manifest/cmd-manifest-kotlin.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'

import { convertGradleToFacts } from './convert-gradle-to-facts.mts'
import { convertGradleToMaven } from './convert_gradle_to_maven.mts'
import { excludePathsFlag } from './manifest-flags.mts'
import { parseBuildToolOpts } from './parse-build-tool-opts.mts'
import { resolveBuildToolBin } from './scripts/build-tool.mts'
import constants, { REQUIREMENTS_TXT, SOCKET_JSON } from '../../constants.mts'
Expand All @@ -16,7 +17,6 @@ import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
import { getFlagListOutput } from '../../utils/output-formatting.mts'
import { readOrDefaultSocketJson } from '../../utils/socket-json.mts'
import { assertValidExcludePaths } from '../scan/exclude-paths.mts'
import { excludePathsFlag } from '../scan/reachability-flags.mts'

import type {
CliCommandConfig,
Expand Down Expand Up @@ -211,8 +211,8 @@ async function run(
}
}
if (includeConfigs === undefined) {
if (sockJson.defaults?.manifest?.gradle?.includeConfigs !== undefined) {
includeConfigs = sockJson.defaults?.manifest?.gradle?.includeConfigs
if (sockJson.defaults?.manifest?.gradle?.includeConfigs) {
includeConfigs = sockJson.defaults.manifest.gradle.includeConfigs
logger.info(
`Using default --include-configs from ${SOCKET_JSON}:`,
includeConfigs,
Expand All @@ -222,8 +222,8 @@ async function run(
}
}
if (excludeConfigs === undefined) {
if (sockJson.defaults?.manifest?.gradle?.excludeConfigs !== undefined) {
excludeConfigs = sockJson.defaults?.manifest?.gradle?.excludeConfigs
if (sockJson.defaults?.manifest?.gradle?.excludeConfigs) {
excludeConfigs = sockJson.defaults.manifest.gradle.excludeConfigs
logger.info(
`Using default --exclude-configs from ${SOCKET_JSON}:`,
excludeConfigs,
Expand Down Expand Up @@ -279,10 +279,13 @@ async function run(
return
}

const javaHome = sockJson.defaults?.manifest?.gradle?.javaHome ?? undefined

if (verbose) {
logger.group()
logger.info('- cwd:', cwd)
logger.info('- gradle bin:', bin)
logger.info('- java home:', javaHome || '(inherited)')
logger.groupEnd()
}

Expand All @@ -305,6 +308,7 @@ async function run(
gradleOpts: parsedGradleOpts,
ignoreUnresolved: Boolean(ignoreUnresolved),
includeConfigs: String(includeConfigs || ''),
javaHome,
verbose: Boolean(verbose),
})
return
Expand Down
2 changes: 1 addition & 1 deletion src/commands/manifest/cmd-manifest-kotlin.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('socket manifest kotlin', async () => {
Options
--bin Location of the gradle binary to use, default: ./gradlew if present, else gradle on PATH
--exclude-configs When generating facts: comma-separated glob patterns; Gradle configurations matching any pattern are skipped (applied after --include-configs)
--exclude-paths List of glob patterns to exclude from the scan, including SCA/SBOM manifest discovery and (when --reach is enabled) full application reachability analysis. Patterns are anchored micromatch globs matched relative to the Socket scan root, which is the command working directory (\`--cwd\` if set), not the reachability target: \`tests\` matches only \`<cwd>/tests\`; use \`**/tests\` to match at any depth. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags.
--exclude-paths List of glob patterns to exclude from manifest/facts generation. Patterns are anchored micromatch globs matched relative to CWD (\`--cwd\` if set): \`tests\` matches only \`<cwd>/tests\`; use \`**/tests\` to match at any depth. Negation patterns (\`!path\`) are not supported. Accepts a comma-separated value or multiple flags.
--facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph. This is the default; pass \`--pom\` to generate \`pom.xml\` files instead
--gradle-opts Additional options to pass on to ./gradlew, see \`./gradlew --help\`
--ignore-unresolved When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)
Expand Down
Loading
Loading