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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [1.1.79](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.79) - 2026-04-08

### Changed
- Updated the Coana CLI to v `14.12.205`.

## [1.1.78](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.78) - 2026-04-01

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "socket",
"version": "1.1.78",
"version": "1.1.79",
"description": "CLI for Socket.dev",
"homepage": "https://github.com/SocketDev/socket-cli",
"license": "MIT AND OFL-1.1",
Expand Down Expand Up @@ -97,7 +97,7 @@
"@babel/preset-typescript": "7.27.1",
"@babel/runtime": "7.28.4",
"@biomejs/biome": "2.2.4",
"@coana-tech/cli": "14.12.201",
"@coana-tech/cli": "14.12.205",
"@cyclonedx/cdxgen": "12.1.2",
"@dotenvx/dotenvx": "1.49.0",
"@eslint/compat": "1.3.2",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 37 additions & 24 deletions src/commands/scan/cmd-scan-reach.e2e.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,33 @@ function findReachabilityForGhsa(
* Logs stdout and stderr to help diagnose test failures.
*/
function logCommandOutput(code: number, stdout: string, stderr: string): void {
logger.error(`Command failed with code ${code}`)
logger.error(`Command exited with code ${code}`)
logger.error('stdout:', stdout)
logger.error('stderr:', stderr)
}

/**
* Log reachability entries that have type "error" for debugging.
* Helps diagnose Coana analysis failures in CI.
*/
function logReachabilityErrors(facts: SocketFactsJson): void {
for (const component of facts.components) {
if (!component.reachability) {
continue
}
for (const ghsaEntry of component.reachability) {
for (const entry of ghsaEntry.reachability) {
if (entry.type === 'error') {
logger.error(
`Reachability error for ${ghsaEntry.ghsa_id} in ${component.name}@${component.version} ` +
`(subproject: ${entry.subprojectPath}): ${JSON.stringify(entry)}`,
)
}
}
}
}
}

describe('socket scan reach (E2E tests)', async () => {
const { binCliPath } = constants
// Standard timeout for most tests.
Expand Down Expand Up @@ -386,8 +408,13 @@ describe('socket scan reach (E2E tests)', async () => {

logger.info('\nReachability analysis completed successfully')
} catch (e) {
if (code !== 0) {
logCommandOutput(code, stdout, stderr)
logCommandOutput(code, stdout, stderr)
// Log reachability errors from the facts file if it was parsed.
try {
const errorFacts = await readSocketFactsJson(tempFixture.path)
logReachabilityErrors(errorFacts)
} catch {
// Facts file may not exist if the failure was earlier.
}
throw e
} finally {
Expand Down Expand Up @@ -483,9 +510,7 @@ describe('socket scan reach (E2E tests)', async () => {
'\nReachability analysis with excluded paths completed successfully',
)
} catch (e) {
if (code !== 0) {
logCommandOutput(code, stdout, stderr)
}
logCommandOutput(code, stdout, stderr)
throw e
} finally {
await tempFixture.cleanup()
Expand Down Expand Up @@ -595,9 +620,7 @@ describe('socket scan reach (E2E tests)', async () => {
'\nReachability analysis with target restriction completed successfully',
)
} catch (e) {
if (code !== 0) {
logCommandOutput(code, stdout, stderr)
}
logCommandOutput(code, stdout, stderr)
throw e
} finally {
await tempFixture.cleanup()
Expand Down Expand Up @@ -673,9 +696,7 @@ describe('socket scan reach (E2E tests)', async () => {
'\nReachability analysis with --cwd flag completed successfully',
)
} catch (e) {
if (code !== 0) {
logCommandOutput(code, stdout, stderr)
}
logCommandOutput(code, stdout, stderr)
throw e
} finally {
await tempFixture.cleanup()
Expand Down Expand Up @@ -771,9 +792,7 @@ describe('socket scan reach (E2E tests)', async () => {
'\nReachability analysis with --cwd and target completed successfully',
)
} catch (e) {
if (code !== 0) {
logCommandOutput(code, stdout, stderr)
}
logCommandOutput(code, stdout, stderr)
throw e
} finally {
await tempFixture.cleanup()
Expand Down Expand Up @@ -915,9 +934,7 @@ describe('socket scan reach (E2E tests)', async () => {
'\nReachability analysis output location verified successfully',
)
} catch (e) {
if (code !== 0) {
logCommandOutput(code, stdout, stderr)
}
logCommandOutput(code, stdout, stderr)
throw e
} finally {
await tempFixture.cleanup()
Expand Down Expand Up @@ -1019,9 +1036,7 @@ describe('socket scan reach (E2E tests)', async () => {
'\nReachability analysis with pypi ecosystem filter completed successfully',
)
} catch (e) {
if (code !== 0) {
logCommandOutput(code, stdout, stderr)
}
logCommandOutput(code, stdout, stderr)
throw e
} finally {
await tempFixture.cleanup()
Expand Down Expand Up @@ -1119,9 +1134,7 @@ describe('socket scan reach (E2E tests)', async () => {
'\nReachability analysis with npm ecosystem filter completed successfully',
)
} catch (e) {
if (code !== 0) {
logCommandOutput(code, stdout, stderr)
}
logCommandOutput(code, stdout, stderr)
throw e
} finally {
await tempFixture.cleanup()
Expand Down
18 changes: 18 additions & 0 deletions src/commands/scan/output-scan-reach.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { logger } from '@socketsecurity/registry/lib/logger'
import { pluralize } from '@socketsecurity/registry/lib/words'

import constants from '../../constants.mts'
import { extractReachabilityErrors } from '../../utils/coana.mts'
import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts'
import { serializeResultJson } from '../../utils/serialize-result-json.mts'

Expand Down Expand Up @@ -29,4 +31,20 @@ export async function outputScanReach(
logger.log('')
logger.success('Reachability analysis completed successfully!')
logger.info(`Reachability report has been written to: ${actualOutputPath}`)

// Warn about individual vulnerabilities where reachability analysis errored.
const errors = extractReachabilityErrors(
result.data.reachabilityReport,
)
if (errors.length) {
logger.log('')
logger.warn(
`Reachability analysis returned ${errors.length} ${pluralize('error', errors.length)} for individual ${pluralize('vulnerability', errors.length)}:`,
)
for (const err of errors) {
logger.warn(
` - ${err.ghsaId} in ${err.componentName}@${err.componentVersion} (${err.subprojectPath})`,
)
}
}
}
53 changes: 53 additions & 0 deletions src/utils/coana.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,59 @@

import { readJsonSync } from '@socketsecurity/registry/lib/fs'

export type ReachabilityError = {
componentName: string
componentVersion: string
ghsaId: string
subprojectPath: string
}

export function extractReachabilityErrors(
socketFactsFile: string,
): ReachabilityError[] {
const json = readJsonSync(socketFactsFile, { throws: false }) as
| {
components?: Array<{
name?: string
reachability?: Array<{
ghsa_id?: string
reachability?: Array<{
subprojectPath?: string
type?: string
}>
}>
version?: string
}>
}
| null
| undefined
if (!json || !Array.isArray(json.components)) {
return []
}
const errors: ReachabilityError[] = []
for (const component of json.components) {
if (!Array.isArray(component.reachability)) {
continue
}
for (const ghsaEntry of component.reachability) {
if (!Array.isArray(ghsaEntry.reachability)) {
continue
}
for (const entry of ghsaEntry.reachability) {
if (entry.type === 'error') {
errors.push({
componentName: String(component.name ?? ''),
componentVersion: String(component.version ?? ''),
ghsaId: String(ghsaEntry.ghsa_id ?? ''),
subprojectPath: String(entry.subprojectPath ?? ''),
})
}
}
}
}
return errors
}

export function extractTier1ReachabilityScanId(
socketFactsFile: string,
): string | undefined {
Expand Down
Loading