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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ 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/).

## [Unreleased]

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

### Fixed
- `socket scan reach` again names the workspace a reachability error came from, instead of reporting every error against the repository root.

## [1.1.156](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.156) - 2026-08-10

### Changed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"@babel/preset-typescript": "7.27.1",
"@babel/runtime": "7.28.4",
"@biomejs/biome": "2.2.4",
"@coana-tech/cli": "15.10.10",
"@coana-tech/cli": "15.10.13",
"@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.

31 changes: 19 additions & 12 deletions src/commands/scan/cmd-scan-reach.e2e.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -327,14 +327,21 @@ describe('socket scan reach (E2E tests)', async () => {
expect(Array.isArray(facts.components)).toBe(true)
expect(Array.isArray(facts.workspaceDiagnostics)).toBe(true)

// Verify workspace diagnostics includes all 3 subprojects.
const subprojectPaths = facts.workspaceDiagnostics.map(
d => d.subprojectPath,
// Verify workspace diagnostics includes all 3 workspaces. Since
// Coana v15.10.8, subprojectPath identifies the build root and
// workspacePath the workspace within it, so this single-root npm
// monorepo reports one `.` subproject holding three workspaces.
const workspacePaths = facts.workspaceDiagnostics.map(
d => d.workspacePath,
)
expect(subprojectPaths).toContain('.')
expect(subprojectPaths).toContain('packages/package-a')
expect(subprojectPaths).toContain('packages/package-b')
expect(workspacePaths).toContain('.')
expect(workspacePaths).toContain('packages/package-a')
expect(workspacePaths).toContain('packages/package-b')
expect(facts.workspaceDiagnostics).toHaveLength(3)
expect(
facts.workspaceDiagnostics.every(d => d.subprojectPath === '.'),
'all workspaces should belong to the single `.` build root',
).toBe(true)

// Verify components count is reasonable (should be > 100 for this workspace).
expect(facts.components.length).toBeGreaterThan(100)
Expand Down Expand Up @@ -694,13 +701,13 @@ describe('socket scan reach (E2E tests)', async () => {
// Read and validate the facts file structure.
const facts = await readSocketFactsJson(tempFixture.path)

// Verify all workspace subprojects are found when using --cwd.
const subprojectPaths = facts.workspaceDiagnostics.map(
d => d.subprojectPath,
// Verify all workspaces are found when using --cwd.
const workspacePaths = facts.workspaceDiagnostics.map(
d => d.workspacePath,
)
expect(subprojectPaths).toContain('.')
expect(subprojectPaths).toContain('packages/package-a')
expect(subprojectPaths).toContain('packages/package-b')
expect(workspacePaths).toContain('.')
expect(workspacePaths).toContain('packages/package-a')
expect(workspacePaths).toContain('packages/package-b')

// Verify we have components.
expect(facts.components.length).toBeGreaterThan(100)
Expand Down
7 changes: 5 additions & 2 deletions src/commands/scan/output-scan-reach.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ 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 {
extractReachabilityErrors,
getFullWorkspacePath,
} 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 @@ -53,7 +56,7 @@ export async function outputScanReach(
)
for (const err of errors) {
logger.warn(
` - ${err.ghsaId} in ${err.componentName}@${err.componentVersion} (${err.subprojectPath})`,
` - ${err.ghsaId} in ${err.componentName}@${err.componentVersion} (${getFullWorkspacePath(err.subprojectPath, err.workspacePath)})`,
)
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/commands/scan/output-scan-reach.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ const errorComponentsBody = {
reachability: [
{
ghsa_id: 'GHSA-aaaa-bbbb-cccc',
reachability: [{ type: 'error', subprojectPath: 'packages/web' }],
reachability: [
{
type: 'error',
subprojectPath: '.',
workspacePath: 'packages/web',
},
],
},
],
},
Expand Down Expand Up @@ -91,5 +97,7 @@ describe('outputScanReach facts-file resolution', () => {
const warned = warnSpy.mock.calls.map(c => String(c[0])).join('\n')
expect(warned).toContain('GHSA-aaaa-bbbb-cccc')
expect(warned).toContain('lodash@4.17.21')
// The workspace, not the `.` build root Coana reports as subprojectPath.
expect(warned).toContain('packages/web')
})
})
21 changes: 21 additions & 0 deletions src/utils/coana.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* to depscan is brotli (api-v0 decodes at the multipart boundary).
* - extractReachabilityErrors: Extract per-component reachability errors
* - extractTier1ReachabilityScanId: Extract scan ID from socket facts file
* - getFullWorkspacePath: Label a build-root/workspace pair the way Coana does
*
* Integration:
* - Works with @coana-tech/cli for reachability analysis
Expand Down Expand Up @@ -120,6 +121,7 @@ export type ReachabilityError = {
componentVersion: string
ghsaId: string
subprojectPath: string
workspacePath: string
}

export function extractReachabilityErrors(
Expand All @@ -134,6 +136,7 @@ export function extractReachabilityErrors(
reachability?: Array<{
subprojectPath?: string
type?: string
workspacePath?: string
}>
}>
version?: string
Expand All @@ -160,6 +163,7 @@ export function extractReachabilityErrors(
componentVersion: String(component.version ?? ''),
ghsaId: String(ghsaEntry.ghsa_id ?? ''),
subprojectPath: String(entry.subprojectPath ?? ''),
workspacePath: String(entry.workspacePath ?? ''),
})
}
}
Expand All @@ -179,3 +183,20 @@ export function extractTier1ReachabilityScanId(
? tier1ReachabilityScanId
: undefined
}

// Label a reachability location the way Coana's own `getFullWorkspacePath`
// does. Since @coana-tech/cli v15.10.8 `subprojectPath` names the build root
// and `workspacePath` the workspace within it, so a single-root repo reports
// `.` as its subproject and must be labelled by its workspace alone.
export function getFullWorkspacePath(
subprojectPath: string,
workspacePath: string,
): string {
const subproject =
subprojectPath && subprojectPath !== '.' ? subprojectPath : ''
const workspace = workspacePath && workspacePath !== '.' ? workspacePath : ''
if (subproject && workspace) {
return `${subproject}/${workspace}`
}
return subproject || workspace || '.'
}
59 changes: 54 additions & 5 deletions src/utils/coana.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* brotli-compressed .br temps, leaves other paths alone, cleans up.
* - extractTier1ReachabilityScanId: plain JSON + edge cases.
* - extractReachabilityErrors: plain JSON + missing + malformed.
* - getFullWorkspacePath: build-root/workspace labelling, including the
* single-root and absent-path cases.
*
* Related Files:
* - utils/coana.mts (implementation)
Expand All @@ -28,6 +30,7 @@ import {
compressSocketFactsForUpload,
extractReachabilityErrors,
extractTier1ReachabilityScanId,
getFullWorkspacePath,
} from './coana.mts'

describe('coana facts-file utils', () => {
Expand Down Expand Up @@ -233,8 +236,16 @@ describe('coana facts-file utils', () => {
{
ghsa_id: 'GHSA-aaaa-bbbb-cccc',
reachability: [
{ type: 'error', subprojectPath: 'packages/web' },
{ type: 'reachable', subprojectPath: 'packages/api' },
{
type: 'error',
subprojectPath: '.',
workspacePath: 'packages/web',
},
{
type: 'reachable',
subprojectPath: '.',
workspacePath: 'packages/api',
},
],
},
],
Expand All @@ -245,7 +256,13 @@ describe('coana facts-file utils', () => {
reachability: [
{
ghsa_id: 'GHSA-xxxx-yyyy-zzzz',
reachability: [{ type: 'error', subprojectPath: 'packages/api' }],
reachability: [
{
type: 'error',
subprojectPath: 'services/gateway',
workspacePath: 'packages/api',
},
],
},
],
},
Expand All @@ -257,13 +274,15 @@ describe('coana facts-file utils', () => {
componentName: 'lodash',
componentVersion: '4.17.21',
ghsaId: 'GHSA-aaaa-bbbb-cccc',
subprojectPath: 'packages/web',
subprojectPath: '.',
workspacePath: 'packages/web',
},
{
componentName: 'axios',
componentVersion: '1.4.0',
ghsaId: 'GHSA-xxxx-yyyy-zzzz',
subprojectPath: 'packages/api',
subprojectPath: 'services/gateway',
workspacePath: 'packages/api',
},
]

Expand Down Expand Up @@ -300,4 +319,34 @@ describe('coana facts-file utils', () => {
expect(extractReachabilityErrors(file)).toEqual([])
})
})

describe('getFullWorkspacePath', () => {
it('joins a build root with the workspace inside it', () => {
expect(getFullWorkspacePath('services/gateway', 'packages/api')).toBe(
'services/gateway/packages/api',
)
})

it('uses the workspace alone for a single-root repo', () => {
expect(getFullWorkspacePath('.', 'packages/api')).toBe('packages/api')
})

it('uses the build root alone when it holds no nested workspace', () => {
expect(getFullWorkspacePath('services/gateway', '.')).toBe(
'services/gateway',
)
})

it('falls back to the root when neither path is meaningful', () => {
expect(getFullWorkspacePath('.', '.')).toBe('.')
expect(getFullWorkspacePath('', '')).toBe('.')
})

it('ignores an absent path from an older Coana release', () => {
expect(getFullWorkspacePath('', 'packages/api')).toBe('packages/api')
expect(getFullWorkspacePath('services/gateway', '')).toBe(
'services/gateway',
)
})
})
})