diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc540168..124a0f0e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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](https://github.com/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.5`. + ## [1.1.119](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.119) - 2026-06-11 ### Changed @@ -101,7 +108,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed - Updated the Coana CLI to v `15.3.9`. -## [1.1.98](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.98) - 2026-05-22 +## [1.1.102](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.98) - 2026-05-22 ### Added - **`socket manifest gradle --facts [beta]`** (and its `socket manifest kotlin --facts` alias) — Emit a `.socket.facts.json` dependency graph from a Gradle build for `socket scan create` to consume as a pregenerated SBOM. Toggle also exposed via the `socket manifest setup` wizard for use with `--auto-manifest`. diff --git a/package.json b/package.json index 82975296c..84925b163 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socket", - "version": "1.1.119", + "version": "1.1.120", "description": "CLI for Socket.dev", "homepage": "https://github.com/SocketDev/socket-cli", "license": "MIT", @@ -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.5", "@cyclonedx/cdxgen": "12.1.2", "@dotenvx/dotenvx": "1.49.0", "@eslint/compat": "1.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d3197590..0e49589ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,8 +128,8 @@ importers: specifier: 2.2.4 version: 2.2.4 '@coana-tech/cli': - specifier: 15.3.26 - version: 15.3.26 + specifier: 15.4.5 + version: 15.4.5 '@cyclonedx/cdxgen': specifier: 12.1.2 version: 12.1.2 @@ -749,8 +749,8 @@ packages: resolution: {integrity: sha512-hAs5PPKPCQ3/Nha+1fo4A4/gL85fIfxZwHPehsjCJ+BhQH2/yw6/xReuaPA/RfNQr6iz1PcD7BZcE3ctyyl3EA==} cpu: [x64] - '@coana-tech/cli@15.3.26': - resolution: {integrity: sha512-l7Jnto1dCOUKVSxdzfJhmv+6VHi57b00z5IjTda45XxbSGVirFJUYxdhNMXjijYYQHc1bUf0b1Nh9VNWhZryug==} + '@coana-tech/cli@15.4.5': + resolution: {integrity: sha512-XH1fTJNZpRQAVErIn1Fntsy+iRHDlpxNTm9O7gjhVSCUVGZxShv7SsVeB+zNVLoTRiLQjJWuGJ5OtueMQHRxHQ==} hasBin: true '@colors/colors@1.5.0': @@ -5385,7 +5385,7 @@ snapshots: '@cdxgen/cdxgen-plugins-bin@2.0.2': optional: true - '@coana-tech/cli@15.3.26': {} + '@coana-tech/cli@15.4.5': {} '@colors/colors@1.5.0': optional: true diff --git a/src/commands/ci/fetch-default-org-slug.test.mts b/src/commands/ci/fetch-default-org-slug.test.mts index 36ef05ac4..0844b0644 100644 --- a/src/commands/ci/fetch-default-org-slug.test.mts +++ b/src/commands/ci/fetch-default-org-slug.test.mts @@ -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(), diff --git a/src/commands/scan/cmd-scan-create.mts b/src/commands/scan/cmd-scan-create.mts index db404f4a9..4221a52b6 100644 --- a/src/commands/scan/cmd-scan-create.mts +++ b/src/commands/scan/cmd-scan-create.mts @@ -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' @@ -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), diff --git a/src/commands/scan/perform-reachability-analysis.mts b/src/commands/scan/perform-reachability-analysis.mts index 0c623dab9..623826ac0 100644 --- a/src/commands/scan/perform-reachability-analysis.mts +++ b/src/commands/scan/perform-reachability-analysis.mts @@ -1,9 +1,13 @@ +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 { 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' @@ -12,10 +16,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 @@ -170,6 +176,24 @@ 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 in the finally below. + let autoManifestConfigPath: string | undefined + const { autoManifestConfig } = reachabilityOptions + if (autoManifestConfig && !isAutoManifestConfigEmpty(autoManifestConfig)) { + autoManifestConfigPath = path.join( + tmpdir(), + `socket-auto-manifest-config-${randomUUID()}.json`, + ) + await fs.writeFile( + autoManifestConfigPath, + JSON.stringify(autoManifestConfig), + 'utf8', + ) + } + // Build Coana arguments. const coanaArgs = [ 'run', @@ -228,6 +252,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] + : []), ] // Build environment variables. @@ -241,48 +270,59 @@ export async function performReachabilityAnalysis( coanaEnv['SOCKET_BRANCH_NAME'] = branchName } - // Run Coana with the manifests tar hash. - const coanaResult = await spawnCoanaDlx(coanaArgs, orgSlug, { - coanaVersion: reachabilityOptions.reachVersion, - cwd, - env: coanaEnv, - spinner, - stdio: 'inherit', - }) + try { + // Run Coana with the manifests tar hash. + const coanaResult = await spawnCoanaDlx(coanaArgs, orgSlug, { + coanaVersion: reachabilityOptions.reachVersion, + cwd, + env: coanaEnv, + spinner, + stdio: 'inherit', + }) - if (wasSpinning) { - spinner.start() - } + if (wasSpinning) { + spinner.start() + } - if (!coanaResult.ok) { - const coanaVersion = - reachabilityOptions.reachVersion || - constants.ENV.INLINED_SOCKET_CLI_COANA_TECH_CLI_VERSION - logger.error( - `Coana reachability analysis failed. Version: ${coanaVersion}, target: ${analysisTarget}, cwd: ${cwd}`, - ) - if (coanaResult.message) { - logger.error(`Details: ${coanaResult.message}`) + if (!coanaResult.ok) { + const coanaVersion = + reachabilityOptions.reachVersion || + constants.ENV.INLINED_SOCKET_CLI_COANA_TECH_CLI_VERSION + logger.error( + `Coana reachability analysis failed. Version: ${coanaVersion}, target: ${analysisTarget}, cwd: ${cwd}`, + ) + if (coanaResult.message) { + logger.error(`Details: ${coanaResult.message}`) + } + return coanaResult } - return coanaResult - } - // Coana writes the facts file relative to the scan `cwd` (it is spawned - // with `cwd` above), so resolve the read path against `cwd` too. Reading - // the bare relative path would resolve against `process.cwd()` and miss - // the file whenever `cwd !== process.cwd()` (e.g. `--cwd