Skip to content

Commit 201a81d

Browse files
authored
create socket scan create reach option for running tier 1 (#704)
* Add --reach option to run tier 1 as part of scan create * pass SOCKET_REPO_NAME, SOCKET_BRANCH_NAME and SOCKET_CLI_VERSION to Coana CLI * update performReachabilityAnalysis to return CResult
1 parent 50e7312 commit 201a81d

File tree

5 files changed

+151
-4
lines changed

5 files changed

+151
-4
lines changed

src/commands/ci/handle-ci.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export async function handleCi(autoManifest: boolean): Promise<void> {
3737
// When 'pendingHead' is true, it requires 'branchName' set and 'tmp' false.
3838
pendingHead: true,
3939
pullRequest: 0,
40+
reach: false,
4041
repoName,
4142
readOnly: false,
4243
report: true,

src/commands/scan/cmd-scan-create.mts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ const config: CliCommandConfig = {
9393
description:
9494
'Similar to --dry-run except it can read from remote, stops before it would create an actual report',
9595
},
96+
reach: {
97+
type: 'boolean',
98+
default: false,
99+
hidden: true,
100+
description:
101+
'Run tier 1 full application reachability analysis during the scanning process',
102+
},
96103
repo: {
97104
type: 'string',
98105
shortFlag: 'r',
@@ -197,6 +204,7 @@ async function run(
197204
markdown,
198205
org: orgFlag,
199206
pullRequest,
207+
reach,
200208
readOnly,
201209
setAsAlertsPage: pendingHeadFlag,
202210
tmp,
@@ -213,6 +221,7 @@ async function run(
213221
org: string
214222
pullRequest: number
215223
readOnly: boolean
224+
reach: boolean
216225
setAsAlertsPage: boolean
217226
tmp: boolean
218227
}
@@ -413,6 +422,7 @@ async function run(
413422
outputKind,
414423
pendingHead: Boolean(pendingHead),
415424
pullRequest: Number(pullRequest),
425+
reach: Boolean(reach),
416426
readOnly: Boolean(readOnly),
417427
repoName,
418428
report,

src/commands/scan/create-scan-from-github.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ async function scanOneRepo(
239239
outputKind,
240240
pendingHead: true,
241241
pullRequest: 0,
242+
reach: false,
242243
readOnly: false,
243244
repoName: repoSlug,
244245
report: false,

src/commands/scan/handle-create-new-scan.mts

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import { fetchSupportedScanFileNames } from './fetch-supported-scan-file-names.m
77
import { handleScanReport } from './handle-scan-report.mts'
88
import { outputCreateNewScan } from './output-create-new-scan.mts'
99
import constants from '../../constants.mts'
10+
import { handleApiCall } from '../../utils/api.mts'
1011
import { checkCommandInput } from '../../utils/check-input.mts'
12+
import { spawnCoana } from '../../utils/coana.mts'
1113
import { getPackageFilesForScan } from '../../utils/path-resolve.mts'
14+
import { setupSdk } from '../../utils/sdk.mts'
1215
import { readOrDefaultSocketJson } from '../../utils/socketjson.mts'
1316
import { detectManifestActions } from '../manifest/detect-manifest-actions.mts'
1417
import { generateAutoManifest } from '../manifest/generate_auto_manifest.mts'
1518

16-
import type { OutputKind } from '../../types.mts'
19+
import type { CResult, OutputKind } from '../../types.mts'
1720

1821
export async function handleCreateNewScan({
1922
autoManifest,
@@ -28,6 +31,7 @@ export async function handleCreateNewScan({
2831
outputKind,
2932
pendingHead,
3033
pullRequest,
34+
reach,
3135
readOnly,
3236
repoName,
3337
report,
@@ -46,6 +50,7 @@ export async function handleCreateNewScan({
4650
pendingHead: boolean
4751
pullRequest: number
4852
outputKind: OutputKind
53+
reach: boolean
4954
readOnly: boolean
5055
repoName: string
5156
report: boolean
@@ -106,8 +111,30 @@ export async function handleCreateNewScan({
106111
return
107112
}
108113

114+
let scanPaths: string[] = packagePaths
115+
116+
// If reachability is enabled, perform reachability analysis
117+
if (reach) {
118+
const reachResult = await performReachabilityAnalysis({
119+
packagePaths,
120+
orgSlug,
121+
cwd,
122+
repoName,
123+
branchName,
124+
outputKind,
125+
interactive,
126+
})
127+
128+
if (!reachResult.ok) {
129+
await outputCreateNewScan(reachResult, outputKind, interactive)
130+
return
131+
}
132+
133+
scanPaths = reachResult.data?.scanPaths || []
134+
}
135+
109136
const fullScanCResult = await fetchCreateOrgFullScan(
110-
packagePaths,
137+
scanPaths,
111138
orgSlug,
112139
{
113140
commitHash,
@@ -153,3 +180,101 @@ export async function handleCreateNewScan({
153180
await outputCreateNewScan(fullScanCResult, outputKind, interactive)
154181
}
155182
}
183+
184+
async function performReachabilityAnalysis({
185+
branchName,
186+
cwd,
187+
orgSlug,
188+
packagePaths,
189+
repoName,
190+
}: {
191+
packagePaths: string[]
192+
orgSlug: string
193+
cwd: string
194+
repoName: string
195+
branchName: string
196+
outputKind: OutputKind
197+
interactive: boolean
198+
}): Promise<CResult<{ scanPaths?: string[] }>> {
199+
logger.info('Starting reachability analysis...')
200+
201+
packagePaths = packagePaths.filter(
202+
p =>
203+
/* Exclude DOT_SOCKET_DOT_FACTS_JSON from previous runs */ !p.includes(
204+
constants.DOT_SOCKET_DOT_FACTS_JSON,
205+
),
206+
)
207+
208+
// Lazily access constants.spinner.
209+
const { spinner } = constants
210+
211+
// Setup SDK for uploading manifests
212+
const sockSdkCResult = await setupSdk()
213+
if (!sockSdkCResult.ok) {
214+
return sockSdkCResult
215+
}
216+
const sockSdk = sockSdkCResult.data
217+
218+
// Upload manifests to get tar hash
219+
spinner.start('Uploading manifests for reachability analysis...')
220+
const uploadCResult = await handleApiCall(
221+
sockSdk.uploadManifestFiles(orgSlug, packagePaths),
222+
{ desc: 'upload manifests' },
223+
)
224+
spinner.stop()
225+
226+
if (!uploadCResult.ok) {
227+
return uploadCResult
228+
}
229+
230+
const tarHash = (uploadCResult.data as { tarHash?: string })?.tarHash
231+
if (!tarHash) {
232+
return {
233+
ok: false,
234+
message: 'Failed to get manifest tar hash',
235+
cause: 'Server did not return a tar hash for the uploaded manifests',
236+
}
237+
}
238+
239+
logger.success(`Manifests uploaded successfully. Tar hash: ${tarHash}`)
240+
241+
// Run Coana with the manifests tar hash
242+
logger.info('Running reachability analysis with Coana...')
243+
const coanaResult = await spawnCoana(
244+
[
245+
'run',
246+
cwd,
247+
'--output-dir',
248+
cwd,
249+
'--socket-mode',
250+
constants.DOT_SOCKET_DOT_FACTS_JSON,
251+
'--disable-report-submission',
252+
'--manifests-tar-hash',
253+
tarHash,
254+
],
255+
{
256+
cwd,
257+
stdio: 'inherit',
258+
env: {
259+
...process.env,
260+
SOCKET_REPO_NAME: repoName,
261+
SOCKET_BRANCH_NAME: branchName,
262+
SOCKET_CLI_VERSION: constants.ENV.INLINED_SOCKET_CLI_VERSION,
263+
},
264+
},
265+
)
266+
267+
if (!coanaResult.ok) {
268+
return coanaResult
269+
}
270+
271+
logger.success('Reachability analysis completed successfully')
272+
273+
// Use the DOT_SOCKET_DOT_FACTS_JSON file for the scan
274+
return {
275+
ok: true,
276+
data: {
277+
scanPaths: [constants.DOT_SOCKET_DOT_FACTS_JSON],
278+
},
279+
}
280+
}

src/utils/coana.mts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { spawn } from '@socketsecurity/registry/lib/spawn'
22

3+
import { getDefaultOrgSlug } from '../commands/ci/fetch-default-org-slug.mts'
34
import constants from '../constants.mts'
45
import { getDefaultToken } from './sdk.mts'
5-
import { getDefaultOrgSlug } from '../commands/ci/fetch-default-org-slug.mts'
66

77
import type { CResult } from '../types.mts'
88
import type {
@@ -15,10 +15,20 @@ export async function spawnCoana(
1515
options?: SpawnOptions | undefined,
1616
extra?: SpawnExtra | undefined,
1717
): Promise<CResult<unknown>> {
18-
const { env: spawnEnv } = { __proto__: null, ...options } as SpawnOptions
18+
const {
19+
env: spawnEnv,
20+
spinner,
21+
stdio,
22+
} = { __proto__: null, ...options } as SpawnOptions
1923
const orgSlugCResult = await getDefaultOrgSlug()
2024
const SOCKET_CLI_API_TOKEN = getDefaultToken()
2125
const SOCKET_ORG_SLUG = orgSlugCResult.ok ? orgSlugCResult.data : undefined
26+
27+
// Stop spinner before streaming output if stdio is 'inherit'
28+
if (stdio === 'inherit' && spinner) {
29+
spinner.stop()
30+
}
31+
2232
try {
2333
const output = await spawn(
2434
constants.execPath,

0 commit comments

Comments
 (0)