Visitar URL original
fix(scan): fail closed instead of crashing Coana when --dynamic-sbom-inference finds no JVM build root by jfblaa · Pull Request #1486 · SocketDev/socket-cli · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
fix(scan): fail closed instead of crashing Coana when --dynamic-sbom-…
…inference finds no JVM build root

Coana was rejecting the run with "--maven-use-only-socket-facts requires
--compute-artifacts-sidecar to also be provided" whenever the flag was
passed with no sidecar to back it, which happened silently on any target
with zero Gradle/sbt/Maven build roots (the sidecar only gets entries
when at least one such root generates facts).

- Only pass --maven-use-only-socket-facts to Coana when a sidecar was
  actually generated, not merely because the user passed
  --dynamic-sbom-inference.
- Abort earlier with a clear InputError when --dynamic-sbom-inference
  finds no Gradle/sbt/Maven build root at all, rather than silently
  proceeding as a normal scan with no indication the flag had no effect.

Linear: REA-710
  • Loading branch information
jfblaa committed Aug 7, 2026
commit 1876e5ebd689778e6c05d9eac2ea700eec55f4ef
14 changes: 11 additions & 3 deletions src/commands/scan/handle-create-new-scan.mts
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,19 @@ export async function handleCreateNewScan({
const generatedFactsPaths = outcomes
.filter(o => o.status === 'generated')
.map(o => o.factsPath!)
if (generatedFactsPaths.length) {
scanTargets = Array.from(
new Set([...scanTargets, ...generatedFactsPaths]),
if (!generatedFactsPaths.length) {
throw new InputError(
[
'No Gradle, sbt, or Maven build root was found.',
'',
'- Remove --dynamic-sbom-inference; it only applies to these ecosystems.',
'- Make sure to run it from the correct dir (use --cwd to target another dir).',
].join('\n'),
Comment thread
jfblaa marked this conversation as resolved.
Resolved
)
}
scanTargets = Array.from(
new Set([...scanTargets, ...generatedFactsPaths]),
)
if (sidecarAcc && hasSidecarEntries(sidecarAcc)) {
resolvedPathsSidecar = serializeSidecar(sidecarAcc)
}
Expand Down
13 changes: 13 additions & 0 deletions src/commands/scan/handle-create-new-scan.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,19 @@ describe('handleCreateNewScan excludePaths', () => {
expect(mockFetchCreateOrgFullScan).not.toHaveBeenCalled()
})

it('aborts when --dynamic-sbom-inference finds no Gradle/sbt/Maven build root', async () => {
mockGenerateRecursiveManifests.mockResolvedValueOnce([])

const config = createConfig({ autoManifest: true, targets: ['/repo'] })
config.reach.dynamicSbomInference = true

await expect(handleCreateNewScan(config)).rejects.toThrow(
/No Gradle, sbt, or Maven build root was found/,
)
expect(mockGetPackageFilesForScan).not.toHaveBeenCalled()
expect(mockFetchCreateOrgFullScan).not.toHaveBeenCalled()
})

it('accumulates a sidecar across recursively discovered build roots and forwards it to reachability analysis', async () => {
mockGenerateRecursiveManifests.mockImplementationOnce(
async ({ sidecarAcc }) => {
Expand Down
5 changes: 4 additions & 1 deletion src/commands/scan/perform-reachability-analysis.mts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,10 @@ export async function performReachabilityAnalysis(
...(reachabilityOptions.reachExcludePaths.length
? ['--exclude-dirs', ...reachabilityOptions.reachExcludePaths]
: []),
...(reachabilityOptions.dynamicSbomInference
// Coana rejects --maven-use-only-socket-facts without a sidecar to back
// it, so this also requires sidecarPath: a target with no Gradle/sbt/
// Maven build root never generates one, even with dynamicSbomInference on.
...(reachabilityOptions.dynamicSbomInference && sidecarPath
? ['--maven-use-only-socket-facts']
: []),
...(reachabilityOptions.reachLazyMode ? ['--lazy-mode'] : []),
Expand Down
77 changes: 77 additions & 0 deletions src/commands/scan/perform-reachability-analysis.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,83 @@ describe('performReachabilityAnalysis timeout/memory forwarding', () => {
})
})

describe('performReachabilityAnalysis --maven-use-only-socket-facts gating', () => {
let scanCwd: string

beforeEach(() => {
vi.clearAllMocks()
mockFetchOrganization.mockResolvedValue({
ok: true,
data: { organizations: {} },
})
mockHasEnterpriseOrgPlan.mockReturnValue(true)
mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: '' })
scanCwd = mkdtempSync(path.join(tmpdir(), 'socket-reamvomsf-'))
})

afterEach(() => {
rmSync(scanCwd, { force: true, recursive: true })
})

it('never passes --maven-use-only-socket-facts without a sidecar, even with dynamicSbomInference on (e.g. no JVM build root found)', async () => {
await performReachabilityAnalysis({
cwd: scanCwd,
reachabilityOptions: {
...makeReachabilityOptions(),
dynamicSbomInference: true,
},
resolvedPathsSidecar: undefined,
target: scanCwd,
})

const args = mockSpawnCoanaDlx.mock.calls[0]![0] as string[]
expect(args).not.toContain('--maven-use-only-socket-facts')
expect(args).not.toContain('--compute-artifacts-sidecar')
})

it('passes --maven-use-only-socket-facts alongside --compute-artifacts-sidecar when dynamicSbomInference is on and a sidecar was generated', async () => {
await performReachabilityAnalysis({
cwd: scanCwd,
reachabilityOptions: {
...makeReachabilityOptions(),
dynamicSbomInference: true,
},
resolvedPathsSidecar: {
'/repo/reactor/.socket.facts.json': {
components: [],
projects: [],
},
},
target: scanCwd,
})

const args = mockSpawnCoanaDlx.mock.calls[0]![0] as string[]
expect(args).toContain('--maven-use-only-socket-facts')
expect(args).toContain('--compute-artifacts-sidecar')
})

it('never passes --maven-use-only-socket-facts when dynamicSbomInference is off, even if a sidecar happens to be present', async () => {
await performReachabilityAnalysis({
cwd: scanCwd,
reachabilityOptions: {
...makeReachabilityOptions(),
dynamicSbomInference: false,
},
resolvedPathsSidecar: {
'/repo/reactor/.socket.facts.json': {
components: [],
projects: [],
},
},
target: scanCwd,
})

const args = mockSpawnCoanaDlx.mock.calls[0]![0] as string[]
expect(args).not.toContain('--maven-use-only-socket-facts')
expect(args).toContain('--compute-artifacts-sidecar')
})
})

describe('performReachabilityAnalysis stdio routing by output kind', () => {
let scanCwd: string

Expand Down