From afbf5cea023b0df0a1e2caf24d740243252dd26e Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Tue, 26 May 2026 20:29:33 +0200 Subject: [PATCH 1/2] fix(coana): strip npm_package_* env vars in dlx fallback to avoid E2BIG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The npm-install fallback inherits the parent's env and spawns `npm install` + `node`. In large monorepos the parent already has hundreds of `npm_package_*` vars populated by npm/pnpm from the root package.json — one per dependency, script, etc. Combined with CI runners' own env blocks (GitHub Actions especially) and PATH, the total can exceed Linux ARG_MAX (~128KB), causing the fallback's own child spawns to fail with E2BIG just like the dlx path did. Strip `npm_package_*` from the env passed to both `npm install` (in `installCoanaToTmpdir`) and the Coana `node` spawn (in `spawnCoanaScriptViaNode`). Coana does not read those vars itself. Preserve `npm_config_*` (registry/proxy/cache from .npmrc) and everything else so nested `npm install` still resolves through any custom registry the user has configured. Symptom: `npm exec @coana-tech/cli ...` exits 249 with `spawn E2BIG` in `runScriptPkg → spawnWithShell` before Coana starts; the fallback trips on exit code >= 128 (already handled) but then hits the same overflow when it spawns `npm install`. With this patch the fallback fits within ARG_MAX and completes. --- src/utils/dlx.mts | 30 ++++++++++++++++++++++++-- src/utils/dlx.test.mts | 49 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/utils/dlx.mts b/src/utils/dlx.mts index e30a6d3ff..4328a0e8c 100644 --- a/src/utils/dlx.mts +++ b/src/utils/dlx.mts @@ -193,6 +193,32 @@ export type CoanaDlxOptions = DlxOptions & { */ const installedCoanaScriptPathsByVersion = new Map() +/** + * Strip npm-injected `npm_package_*` env vars before spawning a Coana + * subprocess. npm (and pnpm/yarn classic) populate one env var per leaf in + * the cwd's package.json — `npm_package_dependencies_*`, `npm_package_scripts_*`, + * etc. In big monorepos with hundreds of deps this can easily account for + * 50KB+ of environment, pushing combined argv + env past Linux ARG_MAX + * (~128KB) and causing `spawn` to fail with E2BIG before Coana even starts. + * + * Coana does not read `npm_package_*` itself, so dropping them is safe. We + * intentionally keep `npm_config_*` (registry, cache, proxy settings sourced + * from .npmrc), `npm_lifecycle_*`, and everything else untouched — those can + * matter for outbound network behavior of nested `npm install` calls. + */ +function sanitizeEnvForCoanaSubprocess( + env: NodeJS.ProcessEnv, +): NodeJS.ProcessEnv { + const out: NodeJS.ProcessEnv = {} + for (const key of Object.keys(env)) { + if (key.startsWith('npm_package_')) { + continue + } + out[key] = env[key] + } + return out +} + /** * Spawn an installed Coana entry point via `node` (or directly, if it's a * native binary). Shared by the SOCKET_CLI_COANA_LOCAL_PATH branch and the @@ -210,7 +236,7 @@ async function spawnCoanaScriptViaNode( const spawnArgs = isBinary ? args : [scriptPath, ...args] const spawnResult = await spawn(isBinary ? scriptPath : 'node', spawnArgs, { cwd: options.cwd, - env: finalEnv, + env: sanitizeEnvForCoanaSubprocess(finalEnv), stdio: spawnExtra?.['stdio'] || 'inherit', }) @@ -278,7 +304,7 @@ async function installCoanaToTmpdir( `@coana-tech/cli@${version}`, ], { - env: finalEnv, + env: sanitizeEnvForCoanaSubprocess(finalEnv), stdio: 'inherit', }, ) diff --git a/src/utils/dlx.test.mts b/src/utils/dlx.test.mts index 44bd32a2a..ce4e59a91 100644 --- a/src/utils/dlx.test.mts +++ b/src/utils/dlx.test.mts @@ -468,5 +468,54 @@ describe('utils/dlx', () => { ) expect(npmInstallCalls).toHaveLength(1) }) + + it('strips npm_package_* env vars in the fallback to avoid E2BIG in big monorepos', async () => { + // Simulate a parent env polluted with npm_package_* (as set by npm/pnpm + // when running inside a project with a populated package.json). The + // fallback must not pass these through to its npm install or node + // spawns, or the same ARG_MAX overflow that broke the dlx path would + // recur. + process.env['npm_package_name'] = 'forge' + process.env['npm_package_dependencies_react'] = '^18.2.0' + process.env['npm_package_devDependencies_typescript'] = '^5.0.0' + // npm_config_* must be preserved — these carry registry/proxy settings + // sourced from .npmrc and are needed for the nested npm install. + process.env['npm_config_registry'] = 'https://artifactory.example/npm/' + + try { + const result = await spawnCoanaDlx(['run', '.'], 'acme', { + coanaVersion: nextVersion(), + }) + expect(result.ok).toBe(true) + + const npmInstallCall = mockSpawn.mock.calls.find( + ([cmd, args]) => cmd === 'npm' && (args as string[])[0] === 'install', + )! + const nodeCall = mockSpawn.mock.calls.find(([cmd]) => cmd === 'node')! + + const npmEnv = (npmInstallCall[2] as { env: NodeJS.ProcessEnv }).env + const nodeEnv = (nodeCall[2] as { env: NodeJS.ProcessEnv }).env + + // npm_package_* are stripped from both spawns. + expect(npmEnv['npm_package_name']).toBeUndefined() + expect(npmEnv['npm_package_dependencies_react']).toBeUndefined() + expect(npmEnv['npm_package_devDependencies_typescript']).toBeUndefined() + expect(nodeEnv['npm_package_name']).toBeUndefined() + expect(nodeEnv['npm_package_dependencies_react']).toBeUndefined() + + // npm_config_* is preserved (registry override survives). + expect(npmEnv['npm_config_registry']).toBe( + 'https://artifactory.example/npm/', + ) + expect(nodeEnv['npm_config_registry']).toBe( + 'https://artifactory.example/npm/', + ) + } finally { + delete process.env['npm_package_name'] + delete process.env['npm_package_dependencies_react'] + delete process.env['npm_package_devDependencies_typescript'] + delete process.env['npm_config_registry'] + } + }) }) }) From 6460cdc9ef2faaed340bc638c31900456e90a72e Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Tue, 26 May 2026 20:36:27 +0200 Subject: [PATCH 2/2] chore: bump version to 1.1.104 Patch release including the npm_package_* env sanitization in the Coana dlx fallback (prevents spawn E2BIG in large monorepos). --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5993e3634..6c2ff8519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed - **Bazel diagnostics** — `socket manifest bazel --verbose` now emits bounded subprocess traces with argv, cwd, duration, exit status, output sizes, and failure stderr tails to make customer log-only triage safer and faster. +## [1.1.104](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.104) - 2026-05-26 + +### Fixed +- Coana CLI invocation: strip `npm_package_*` env vars before spawning the npm-install fallback. Prevents `spawn E2BIG` failures in large monorepos where the parent process has hundreds of `npm_package_*` env vars populated from the root `package.json`. Preserves `npm_config_*` (registry / proxy / cache from `.npmrc`). + ## [1.1.103](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.103) - 2026-05-26 ### Changed diff --git a/package.json b/package.json index 80cd5aa37..eb82d5031 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socket", - "version": "1.1.103", + "version": "1.1.104", "description": "CLI for Socket.dev", "homepage": "https://github.com/SocketDev/socket-cli", "license": "MIT AND OFL-1.1",