socket-cli: --dynamic-sbom-inference flag for per-workspace Maven reachability - #1451
Merged
Jeppe Fredsgaard Blaabjerg (jfblaa) merged 4 commits intoJul 31, 2026
Conversation
…e Maven reachability Adds a hidden --dynamic-sbom-inference flag to `socket scan create` and `socket scan reach`. Setting it forces --auto-manifest on to generate per-workspace Socket facts, and passes --maven-use-only-root-socket-facts to the Coana CLI so it consumes those pre-generated per-workspace facts instead of resolving the full Maven reactor itself. Also fixes assemble.mts: when a dependency edge resolves to a first-party project (a subproject referenced in dependency position), the sidecar now reports that project's own build source/target roots instead of the classpath-resolved artifact path, since the corresponding jar may not exist if the module hasn't been built yet. Sources are now sorted for determinism, matching targets. Blocked on a Coana CLI release supporting --maven-use-only-root-socket-facts. Linear: REA-687
Jeppe Fredsgaard Blaabjerg (jfblaa)
marked this pull request as ready for review
July 30, 2026 19:47
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Reach skips auto-manifest for SBOM flag
- Confirmed real: scan reach now runs generateAutoManifest with computeArtifactsSidecar and forwards resolvedPathsSidecar when --dynamic-sbom-inference is set, matching scan create.
Or push these changes by commenting:
@cursor push 53b1f33147
Preview (53b1f33147)
diff --git a/src/commands/scan/handle-scan-reach.mts b/src/commands/scan/handle-scan-reach.mts
--- a/src/commands/scan/handle-scan-reach.mts
+++ b/src/commands/scan/handle-scan-reach.mts
@@ -10,9 +10,13 @@
import { checkCommandInput } from '../../utils/check-input.mts'
import { findSocketYmlSync } from '../../utils/config.mts'
import { getPackageFilesForScan } from '../../utils/path-resolve.mts'
+import { readOrDefaultSocketJson } from '../../utils/socket-json.mts'
+import { detectManifestActions } from '../manifest/detect-manifest-actions.mts'
+import { generateAutoManifest } from '../manifest/generate_auto_manifest.mts'
import type { ReachabilityOptions } from './perform-reachability-analysis.mts'
import type { OutputKind } from '../../types.mts'
+import type { ResolvedPathsSidecar } from '../manifest/scripts/sidecar.mts'
export type HandleScanReachConfig = {
cwd: string
@@ -35,6 +39,33 @@
}: HandleScanReachConfig) {
const { spinner } = constants
+ // --dynamic-sbom-inference implies auto-manifest so Coana can consume
+ // per-workspace Socket facts via --maven-use-only-root-socket-facts.
+ let scanTargets = targets
+ let resolvedPathsSidecar: ResolvedPathsSidecar | undefined
+ if (reachabilityOptions.dynamicSbomInference) {
+ logger.info('Auto-generating manifest files ...')
+ const sockJson = readOrDefaultSocketJson(cwd)
+ const detected = await detectManifestActions(sockJson, cwd)
+ const autoManifestResult = await generateAutoManifest({
+ computeArtifactsSidecar: true,
+ cwd,
+ detected,
+ excludePaths: reachabilityOptions.excludePaths,
+ outputKind,
+ verbose: false,
+ })
+ resolvedPathsSidecar = autoManifestResult.resolvedPathsSidecar
+ if (autoManifestResult.generatedFiles.length) {
+ scanTargets = Array.from(
+ new Set([...targets, ...autoManifestResult.generatedFiles]),
+ )
+ }
+ logger.info(
+ 'Auto-generation finished. Proceeding with reachability analysis.',
+ )
+ }
+
// Get supported file names.
const supportedFilesCResult = await fetchSupportedScanFileNames({
orgSlug,
@@ -68,11 +99,15 @@
target: targets[0]!,
})
- const packagePaths = await getPackageFilesForScan(targets, supportedFiles, {
- additionalIgnores: additionalScaIgnores,
- config: socketConfig,
- cwd,
- })
+ const packagePaths = await getPackageFilesForScan(
+ scanTargets,
+ supportedFiles,
+ {
+ additionalIgnores: additionalScaIgnores,
+ config: socketConfig,
+ cwd,
+ },
+ )
spinner.successAndStop(
`Found ${packagePaths.length} ${pluralize('manifest file', packagePaths.length)} for reachability analysis.`,
@@ -102,6 +137,7 @@
outputPath,
packagePaths,
reachabilityOptions: mergedReachabilityOptions,
+ resolvedPathsSidecar,
spinner,
target: targets[0]!,
uploadManifests: true,
diff --git a/src/commands/scan/handle-scan-reach.test.mts b/src/commands/scan/handle-scan-reach.test.mts
--- a/src/commands/scan/handle-scan-reach.test.mts
+++ b/src/commands/scan/handle-scan-reach.test.mts
@@ -4,25 +4,33 @@
const {
mockCheckCommandInput,
+ mockDetectManifestActions,
mockFetchSupportedScanFileNames,
mockFinalizeTier1Scan,
mockFindSocketYmlSync,
+ mockGenerateAutoManifest,
mockGetPackageFilesForScan,
+ mockLoggerInfo,
mockLoggerSuccess,
mockLoggerWarn,
mockOutputScanReach,
mockPerformReachabilityAnalysis,
+ mockReadOrDefaultSocketJson,
mockSentryInternalsSymbol,
} = vi.hoisted(() => ({
mockCheckCommandInput: vi.fn(),
+ mockDetectManifestActions: vi.fn(),
mockFetchSupportedScanFileNames: vi.fn(),
mockFinalizeTier1Scan: vi.fn(),
mockFindSocketYmlSync: vi.fn(),
+ mockGenerateAutoManifest: vi.fn(),
mockGetPackageFilesForScan: vi.fn(),
+ mockLoggerInfo: vi.fn(),
mockLoggerSuccess: vi.fn(),
mockLoggerWarn: vi.fn(),
mockOutputScanReach: vi.fn(),
mockPerformReachabilityAnalysis: vi.fn(),
+ mockReadOrDefaultSocketJson: vi.fn(),
mockSentryInternalsSymbol: Symbol('kInternalsSymbol'),
}))
@@ -72,8 +80,21 @@
getPackageFilesForScan: mockGetPackageFilesForScan,
}))
+vi.mock('../../utils/socket-json.mts', () => ({
+ readOrDefaultSocketJson: mockReadOrDefaultSocketJson,
+}))
+
+vi.mock('../manifest/detect-manifest-actions.mts', () => ({
+ detectManifestActions: mockDetectManifestActions,
+}))
+
+vi.mock('../manifest/generate_auto_manifest.mts', () => ({
+ generateAutoManifest: mockGenerateAutoManifest,
+}))
+
vi.mock('@socketsecurity/registry/lib/logger', () => ({
logger: {
+ info: mockLoggerInfo,
success: mockLoggerSuccess,
warn: mockLoggerWarn,
},
@@ -83,6 +104,7 @@
beforeEach(() => {
vi.clearAllMocks()
mockCheckCommandInput.mockReturnValue(true)
+ mockDetectManifestActions.mockResolvedValue({ count: 0 })
mockFetchSupportedScanFileNames.mockResolvedValue({
ok: true,
data: { npm: { packageJson: { pattern: 'package.json' } } },
@@ -92,6 +114,7 @@
ok: true,
data: { parsed: { projectIgnorePaths: ['vendor/**'] } },
})
+ mockGenerateAutoManifest.mockResolvedValue({ generatedFiles: [] })
mockGetPackageFilesForScan.mockResolvedValue(['package.json'])
mockPerformReachabilityAnalysis.mockResolvedValue({
ok: true,
@@ -100,6 +123,7 @@
tier1ReachabilityScanId: undefined,
},
})
+ mockReadOrDefaultSocketJson.mockReturnValue({})
})
it('applies excludePaths to manifest discovery and reachability analysis', async () => {
@@ -137,6 +161,7 @@
targets: ['.'],
})
+ expect(mockGenerateAutoManifest).not.toHaveBeenCalled()
expect(mockGetPackageFilesForScan).toHaveBeenCalledWith(
['.'],
{ npm: { packageJson: { pattern: 'package.json' } } },
@@ -151,10 +176,86 @@
reachabilityOptions: expect.objectContaining({
reachExcludePaths: ['node_modules', 'tests', 'packages/*'],
}),
+ resolvedPathsSidecar: undefined,
}),
)
})
+ it('runs auto-manifest and forwards sidecar when dynamicSbomInference is set', async () => {
+ const resolvedPathsSidecar = [
+ {
+ classifier: null,
+ ext: 'jar',
+ group: 'g',
+ name: 'n',
+ sources: [],
+ targets: [],
+ version: '1',
+ },
+ ]
+ mockGenerateAutoManifest.mockResolvedValueOnce({
+ generatedFiles: ['/repo/.socket.facts.json'],
+ resolvedPathsSidecar,
+ })
+
+ const reachabilityOptions = {
+ dynamicSbomInference: true,
+ excludePaths: ['tests'],
+ reachAnalysisMemoryLimit: '8192',
+ reachAnalysisTimeout: '',
+ reachConcurrency: 1,
+ reachContinueOnAnalysisErrors: false,
+ reachContinueOnInstallErrors: false,
+ reachContinueOnMissingLockFiles: false,
+ reachContinueOnNoSourceFiles: false,
+ reachDebug: false,
+ reachDetailedAnalysisLogFile: false,
+ reachDisableAnalytics: false,
+ reachDisableExternalToolChecks: false,
+ reachEcosystems: [],
+ reachEnableAnalysisSplitting: false,
+ reachExcludePaths: [],
+ reachLazyMode: false,
+ reachRetainFactsFile: false,
+ reachSkipCache: false,
+ reachUseOnlyPregeneratedSboms: false,
+ reachVersion: undefined,
+ }
+
+ await handleScanReach({
+ cwd: '/repo',
+ interactive: false,
+ orgSlug: 'fakeOrg',
+ outputKind: 'text',
+ outputPath: '',
+ reachabilityOptions,
+ targets: ['.'],
+ })
+
+ expect(mockGenerateAutoManifest).toHaveBeenCalledWith({
+ computeArtifactsSidecar: true,
+ cwd: '/repo',
+ detected: { count: 0 },
+ excludePaths: ['tests'],
+ outputKind: 'text',
+ verbose: false,
+ })
+ expect(mockGetPackageFilesForScan).toHaveBeenCalledWith(
+ ['.', '/repo/.socket.facts.json'],
+ { npm: { packageJson: { pattern: 'package.json' } } },
+ {
+ additionalIgnores: ['tests', 'tests/**'],
+ config: { projectIgnorePaths: ['vendor/**'] },
+ cwd: '/repo',
+ },
+ )
+ expect(mockPerformReachabilityAnalysis).toHaveBeenCalledWith(
+ expect.objectContaining({
+ resolvedPathsSidecar,
+ }),
+ )
+ })
+
it('translates excludePaths from the scan root for nested targets', async () => {
const reachabilityOptions = {
dynamicSbomInference: false,You can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 1d0c692. Configure here.
Martin Torp (mtorp)
approved these changes
Jul 31, 2026
socket scan reach never runs auto-manifest, so honoring the flag there passed Coana --maven-use-only-root-socket-facts without the per-workspace Socket facts it depends on ever being generated, producing incorrect or failed reachability analysis. The flag is hidden/internal and scan reach is a hidden command, so this is disabled quietly rather than adding a new validation error.
…ence-scan-flag-for-per
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Jeppe Fredsgaard Blaabjerg (jfblaa)
enabled auto-merge (squash)
July 31, 2026 07:21
Jeppe Fredsgaard Blaabjerg (jfblaa)
deleted the
jfblaa/rea-687-socket-cli-dynamic-sbom-inference-scan-flag-for-per
branch
July 31, 2026 07:22
3 tasks
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
--dynamic-sbom-inferenceflag tosocket scan create(andsocket scan reach) that forces--auto-manifeston and passes--maven-use-only-root-socket-factsthrough to the Coana CLI, so Coana consumes pre-generated per-workspace Socket facts instead of resolving the full Maven reactor itself.assemble.mtsso a dependency edge that resolves to a first-party project reports that project's own build source/target roots instead of a classpath-resolved artifact path that may not exist if the module hasn't been built yet.Status: draft, not mergeable yet
This PR is blocked until the Coana CLI supports
--maven-use-only-root-socket-factsand@coana-tech/cliis bumped + published in this repo. Until then it can only be exercised via a local Coana build (SOCKET_CLI_COANA_LOCAL_PATH).Linear: REA-687
Test plan
pnpm check:tsc/pnpm check:lintcleanpnpm build(rollup + tsgo) succeedsperform-reachability-analysis,handle-scan-reach,handle-create-new-scan,exclude-paths)--reach --dynamic-sbom-inferencesmoke test once Coana CLI is publishedNote
Medium Risk
Changes Maven reachability inputs and Coana CLI wiring; wrong artifact paths could skew analysis, but the flag is hidden and defaults off.
Overview
Adds a hidden
--dynamic-sbom-inferenceflag onsocket scan createandsocket scan reach. When set (with--reach), it turns on--auto-manifestso per-workspace Socket facts are generated, and forwards--maven-use-only-root-socket-factsto Coana so Maven reachability uses those pre-generated facts instead of resolving the full reactor inside Coana.assemble.mtsnow maps first-party Maven modules to only the project’s own build targets (not merged with classpath artifact paths from dependency nodes), and sorts sources—so reachability points at module source roots that exist before a local build.Bumps
@coana-tech/clito 15.9.9 and releases 1.1.151.Reviewed by Cursor Bugbot for commit 1d0c692. Configure here.