From c5b6b7b62e98c5a55d7aca1462db18ad14ec8be6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 16:43:18 +0000 Subject: [PATCH] Fix command injection vulnerability Replaced child_process.execSync with execFileSync array-based arguments and specifically process.execPath for node scripts. Co-authored-by: connerkup <72580335+connerkup@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ packages/core/src/loop.test.ts | 18 +++++++++++------- packages/core/src/reconcile.ts | 4 ++-- 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..31524f7 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2026-11-11 - [CRITICAL] Command Injection via execSync +**Vulnerability:** The codebase was using `child_process.execSync` which invokes a shell, making it susceptible to command injection attacks if user inputs or environment variables are not properly sanitized. +**Learning:** `execSync` executes commands within a shell environment which automatically expands shell variables and parses metacharacters, a significant security risk for a local safety harness meant for potentially adversarial agents. +**Prevention:** Always use `child_process.execFileSync` instead. It does not spawn a shell by default, meaning arguments are passed exactly as provided without expansion or interpretation. Use array-based arguments and specifically `process.execPath` for node scripts. diff --git a/packages/core/src/loop.test.ts b/packages/core/src/loop.test.ts index f9d430f..487ffc6 100644 --- a/packages/core/src/loop.test.ts +++ b/packages/core/src/loop.test.ts @@ -1,4 +1,4 @@ -import { execSync } from "node:child_process"; +import { execFileSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -173,12 +173,16 @@ describe("ledger benchmark e2e", () => { it("reconcile passes validator for correct ledger implementation", () => { const dir = setupWorkspace(); const mergeTarget = path.join(dir, "auth_service.js"); - execSync(`node "${fixturePath("mock-dev-ledger.mjs")}" "${path.join(dir, "developer_output.json")}"`, { - stdio: "pipe", - }); - execSync(`node "${fixturePath("mock-audit-pass.mjs")}" "${path.join(dir, "auditor_output.json")}"`, { - stdio: "pipe", - }); + execFileSync( + process.execPath, + [fixturePath("mock-dev-ledger.mjs"), path.join(dir, "developer_output.json")], + { stdio: "pipe" } + ); + execFileSync( + process.execPath, + [fixturePath("mock-audit-pass.mjs"), path.join(dir, "auditor_output.json")], + { stdio: "pipe" } + ); const result = reconcile({ workspace: dir, diff --git a/packages/core/src/reconcile.ts b/packages/core/src/reconcile.ts index b0285bc..7b04b7f 100644 --- a/packages/core/src/reconcile.ts +++ b/packages/core/src/reconcile.ts @@ -1,4 +1,4 @@ -import { execSync } from "node:child_process"; +import { execFileSync } from "node:child_process"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; @@ -139,7 +139,7 @@ export function reconcile(options: ReconcileOptions): ReconcileResult { fs.writeFileSync(tempFile, extractedCode, "utf8"); try { - execSync(`node --check "${tempFile}"`, { stdio: "pipe" }); + execFileSync(process.execPath, ["--check", tempFile], { stdio: "pipe" }); log.audit("Syntax check passed"); } catch (err) { const stderr =