From 560dfcce0953041d6622ca41aff873c59ecdf113 Mon Sep 17 00:00:00 2001 From: AirCoding Date: Thu, 4 Jun 2026 17:35:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(P3):=20eliminate=20all=20execSync=20usage?= =?UTF-8?q?=20=E2=80=94=20uniform=20execFileSync=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3 P3 residuals found in independent audit, all non-exploitable but inconsistent with the project security pattern (execFileSync + args array): 1. WorkerManager.find_bun: 'which bun' + 'test -x ${path}' replaced with existsSync() + hardcoded candidates (no shell). BUN_INSTALL env var added as first candidate. 2. CppTestRunner: 'ctest --output-on-failure' (literal string, safe but inconsistent) → execFileSync('ctest', ['--output-on-failure'], ...). 3. e2e.ts: 4 execSync calls (find tools + run depcruise/tsc) replaced with execFileSync + args arrays. Removed unused findDepcruise(). Inlined the 7 package paths instead of relying on shell glob expansion. Verification: - grep 'execSync' across packages/cli + packages/runtime/src + packages/toolchain-cpp/src returns 0 matches - 28 execFileSync usages (uniform pattern) - 169/169 tests pass - tsc --noEmit: 0 errors Co-Authored-By: Claude Opus 4.8 --- packages/cli/src/commands/e2e.ts | 28 +++++++++---------- packages/runtime/src/workers/WorkerManager.ts | 26 ++++++++--------- .../toolchain-cpp/src/test/CppTestRunner.ts | 4 +-- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/packages/cli/src/commands/e2e.ts b/packages/cli/src/commands/e2e.ts index a9f749f..f8294eb 100755 --- a/packages/cli/src/commands/e2e.ts +++ b/packages/cli/src/commands/e2e.ts @@ -2,38 +2,36 @@ * E2ECommand - Run end-to-end validation * DD §17. Every gate executes a real check (no file existence or hardcoded outputs). */ -import { execSync } from 'child_process' +import { execFileSync } from 'child_process' import { existsSync } from 'fs' import { join } from 'path' function findBun(): string { - try { return execSync('which bun', { encoding: 'utf-8' }).trim() } catch {} + // Try common paths first (no shell) const candidates = [ + process.env.BUN_INSTALL ? `${process.env.BUN_INSTALL}/bin/bun` : null, join(process.env.HOME || '/root', '.bun', 'bin', 'bun'), '/usr/local/bin/bun', '/usr/bin/bun' - ] + ].filter((p): p is string => Boolean(p)) for (const c of candidates) { if (existsSync(c)) return c } - throw new Error('bun not found — cannot run E2E tests') + // PATH fallback + return 'bun' } function findTsc(): string { - try { return execSync('node_modules/.bin/tsc', { encoding: 'utf-8' }).trim() } catch {} - return './node_modules/.bin/tsc' -} - -function findDepcruise(): string { - try { return execSync('npx --no-install depcruise', { encoding: 'utf-8' }).trim() } catch {} - return 'npx --no-install depcruise' + const local = './node_modules/.bin/tsc' + return existsSync(local) ? local : 'tsc' } /** - * Run a command and return pass/fail + error excerpt. + * Run a command using execFileSync (no shell, no string interpolation). + * Args are passed as an array — safe against command injection. */ function runCmd(label: string, cmd: string, args: string[], cwd?: string, timeoutMs = 180000): { pass: boolean; detail: string } { try { - const output = execSync([cmd, ...args].join(' '), { + execFileSync(cmd, args, { cwd: cwd || process.cwd(), encoding: 'utf-8', stdio: 'pipe', @@ -74,7 +72,7 @@ export function e2eCommand(): void { }}, { label: 'P0: depcruise dependency boundary (INV-4)', fn: () => { try { - execSync('node_modules/.bin/depcruise --config .dependency-cruiser.js packages/*/src/ 2>&1', { + execFileSync('node_modules/.bin/depcruise', ['--config', '.dependency-cruiser.js', 'packages/cli/src/', 'packages/contracts/src/', 'packages/llm/src/', 'packages/runtime/src/', 'packages/toolchain-cpp/src/', 'packages/tui/src/', 'packages/workers/src/'], { encoding: 'utf-8', stdio: 'pipe', timeout: 60000 }) return { pass: true, detail: '✅' } @@ -85,7 +83,7 @@ export function e2eCommand(): void { { label: 'P0: tsc strict typecheck (0 errors)', fn: () => { const tsc = findTsc() try { - execSync(`${tsc} --noEmit -p tsconfig.check.json`, { encoding: 'utf-8', stdio: 'pipe', timeout: 90000 }) + execFileSync(tsc, ['--noEmit', '-p', 'tsconfig.check.json'], { encoding: 'utf-8', stdio: 'pipe', timeout: 90000 }) return { pass: true, detail: '✅' } } catch (err: any) { const stdout = err.stdout || '' diff --git a/packages/runtime/src/workers/WorkerManager.ts b/packages/runtime/src/workers/WorkerManager.ts index b4e4e2a..38442cb 100755 --- a/packages/runtime/src/workers/WorkerManager.ts +++ b/packages/runtime/src/workers/WorkerManager.ts @@ -7,7 +7,8 @@ * @module packages/runtime/src/workers/WorkerManager */ -import { spawn, execSync } from 'child_process' +import { spawn } from 'child_process' +import { existsSync } from 'fs' import type { ChildProcess } from 'child_process' import { WorkerProcess, type WorkerExitCode } from './WorkerProcess.js' import { WorkerProtocol, type WorkerMessage, type WorkerMessageType } from './WorkerProtocol.js' @@ -226,18 +227,17 @@ export class WorkerManager { } private find_bun(): string { - try { - return execSync('which bun', { encoding: 'utf-8' }).trim() - } catch { - // Try common paths - const common = ['/home/airlongdian/.bun/bin/bun', '/usr/local/bin/bun', '/usr/bin/bun'] - for (const path of common) { - try { - execSync(`test -x ${path}`) - return path - } catch { /* */ } - } - return 'bun' + // Try common paths first (no shell, no string interpolation) + const candidates = [ + process.env.BUN_INSTALL ? `${process.env.BUN_INSTALL}/bin/bun` : null, + `${process.env.HOME || '/root'}/.bun/bin/bun`, + '/usr/local/bin/bun', + '/usr/bin/bun', + ].filter((p): p is string => Boolean(p)) + + for (const candidate of candidates) { + if (existsSync(candidate)) return candidate } + return 'bun' // PATH fallback } } diff --git a/packages/toolchain-cpp/src/test/CppTestRunner.ts b/packages/toolchain-cpp/src/test/CppTestRunner.ts index 1a41362..5f0e11d 100755 --- a/packages/toolchain-cpp/src/test/CppTestRunner.ts +++ b/packages/toolchain-cpp/src/test/CppTestRunner.ts @@ -5,7 +5,7 @@ * @module packages/toolchain-cpp/src/test/CppTestRunner */ -import { execSync } from 'child_process' +import { execFileSync } from 'child_process' import { DiagnosticParser } from '../analysis/DiagnosticParser.js' export interface CppTestOutput { @@ -22,7 +22,7 @@ export class CppTestRunner { const start = Date.now() try { - const output = execSync('ctest --output-on-failure', { + const output = execFileSync('ctest', ['--output-on-failure'], { cwd: build_dir, encoding: 'utf-8', stdio: 'pipe'