fix(P3): eliminate all execSync usage — uniform execFileSync pattern
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 <noreply@anthropic.com>
This commit is contained in:
@@ -2,38 +2,36 @@
|
|||||||
* E2ECommand - Run end-to-end validation
|
* E2ECommand - Run end-to-end validation
|
||||||
* DD §17. Every gate executes a real check (no file existence or hardcoded outputs).
|
* 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 { existsSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
function findBun(): string {
|
function findBun(): string {
|
||||||
try { return execSync('which bun', { encoding: 'utf-8' }).trim() } catch {}
|
// Try common paths first (no shell)
|
||||||
const candidates = [
|
const candidates = [
|
||||||
|
process.env.BUN_INSTALL ? `${process.env.BUN_INSTALL}/bin/bun` : null,
|
||||||
join(process.env.HOME || '/root', '.bun', 'bin', 'bun'),
|
join(process.env.HOME || '/root', '.bun', 'bin', 'bun'),
|
||||||
'/usr/local/bin/bun', '/usr/bin/bun'
|
'/usr/local/bin/bun', '/usr/bin/bun'
|
||||||
]
|
].filter((p): p is string => Boolean(p))
|
||||||
for (const c of candidates) {
|
for (const c of candidates) {
|
||||||
if (existsSync(c)) return c
|
if (existsSync(c)) return c
|
||||||
}
|
}
|
||||||
throw new Error('bun not found — cannot run E2E tests')
|
// PATH fallback
|
||||||
|
return 'bun'
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTsc(): string {
|
function findTsc(): string {
|
||||||
try { return execSync('node_modules/.bin/tsc', { encoding: 'utf-8' }).trim() } catch {}
|
const local = './node_modules/.bin/tsc'
|
||||||
return './node_modules/.bin/tsc'
|
return existsSync(local) ? local : 'tsc'
|
||||||
}
|
|
||||||
|
|
||||||
function findDepcruise(): string {
|
|
||||||
try { return execSync('npx --no-install depcruise', { encoding: 'utf-8' }).trim() } catch {}
|
|
||||||
return 'npx --no-install depcruise'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 } {
|
function runCmd(label: string, cmd: string, args: string[], cwd?: string, timeoutMs = 180000): { pass: boolean; detail: string } {
|
||||||
try {
|
try {
|
||||||
const output = execSync([cmd, ...args].join(' '), {
|
execFileSync(cmd, args, {
|
||||||
cwd: cwd || process.cwd(),
|
cwd: cwd || process.cwd(),
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
@@ -74,7 +72,7 @@ export function e2eCommand(): void {
|
|||||||
}},
|
}},
|
||||||
{ label: 'P0: depcruise dependency boundary (INV-4)', fn: () => {
|
{ label: 'P0: depcruise dependency boundary (INV-4)', fn: () => {
|
||||||
try {
|
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
|
encoding: 'utf-8', stdio: 'pipe', timeout: 60000
|
||||||
})
|
})
|
||||||
return { pass: true, detail: '✅' }
|
return { pass: true, detail: '✅' }
|
||||||
@@ -85,7 +83,7 @@ export function e2eCommand(): void {
|
|||||||
{ label: 'P0: tsc strict typecheck (0 errors)', fn: () => {
|
{ label: 'P0: tsc strict typecheck (0 errors)', fn: () => {
|
||||||
const tsc = findTsc()
|
const tsc = findTsc()
|
||||||
try {
|
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: '✅' }
|
return { pass: true, detail: '✅' }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const stdout = err.stdout || ''
|
const stdout = err.stdout || ''
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
* @module packages/runtime/src/workers/WorkerManager
|
* @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 type { ChildProcess } from 'child_process'
|
||||||
import { WorkerProcess, type WorkerExitCode } from './WorkerProcess.js'
|
import { WorkerProcess, type WorkerExitCode } from './WorkerProcess.js'
|
||||||
import { WorkerProtocol, type WorkerMessage, type WorkerMessageType } from './WorkerProtocol.js'
|
import { WorkerProtocol, type WorkerMessage, type WorkerMessageType } from './WorkerProtocol.js'
|
||||||
@@ -226,18 +227,17 @@ export class WorkerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private find_bun(): string {
|
private find_bun(): string {
|
||||||
try {
|
// Try common paths first (no shell, no string interpolation)
|
||||||
return execSync('which bun', { encoding: 'utf-8' }).trim()
|
const candidates = [
|
||||||
} catch {
|
process.env.BUN_INSTALL ? `${process.env.BUN_INSTALL}/bin/bun` : null,
|
||||||
// Try common paths
|
`${process.env.HOME || '/root'}/.bun/bin/bun`,
|
||||||
const common = ['/home/airlongdian/.bun/bin/bun', '/usr/local/bin/bun', '/usr/bin/bun']
|
'/usr/local/bin/bun',
|
||||||
for (const path of common) {
|
'/usr/bin/bun',
|
||||||
try {
|
].filter((p): p is string => Boolean(p))
|
||||||
execSync(`test -x ${path}`)
|
|
||||||
return path
|
for (const candidate of candidates) {
|
||||||
} catch { /* */ }
|
if (existsSync(candidate)) return candidate
|
||||||
}
|
|
||||||
return 'bun'
|
|
||||||
}
|
}
|
||||||
|
return 'bun' // PATH fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* @module packages/toolchain-cpp/src/test/CppTestRunner
|
* @module packages/toolchain-cpp/src/test/CppTestRunner
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execSync } from 'child_process'
|
import { execFileSync } from 'child_process'
|
||||||
import { DiagnosticParser } from '../analysis/DiagnosticParser.js'
|
import { DiagnosticParser } from '../analysis/DiagnosticParser.js'
|
||||||
|
|
||||||
export interface CppTestOutput {
|
export interface CppTestOutput {
|
||||||
@@ -22,7 +22,7 @@ export class CppTestRunner {
|
|||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const output = execSync('ctest --output-on-failure', {
|
const output = execFileSync('ctest', ['--output-on-failure'], {
|
||||||
cwd: build_dir,
|
cwd: build_dir,
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
stdio: 'pipe'
|
stdio: 'pipe'
|
||||||
|
|||||||
Reference in New Issue
Block a user