From 6e0c6372040db416381cbdd5b8be7cf6a3085ee1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:56:14 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20Command=20Injection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced `execSync` with `spawnSync` in `packages/core/src/reconcile.ts` to prevent command injection vulnerabilities. `execSync` evaluated a dynamically constructed command containing file paths that could have shell metacharacters. `spawnSync` provides a safer execution using argument arrays. Error handling was adapted to handle `spawnSync` output properly. Co-authored-by: connerkup <72580335+connerkup@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ packages/core/src/reconcile.ts | 16 +++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..a375fa7 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2026-10-24 - [Command Injection via execSync in Reconciler] +**Vulnerability:** Use of execSync with a dynamically constructed string containing a file path. +**Learning:** File paths can contain shell metacharacters which could lead to command injection if passed directly to execSync. +**Prevention:** Always use spawnSync with an array of arguments, avoiding the use of a shell where possible. diff --git a/packages/core/src/reconcile.ts b/packages/core/src/reconcile.ts index b0285bc..104bcf3 100644 --- a/packages/core/src/reconcile.ts +++ b/packages/core/src/reconcile.ts @@ -1,4 +1,4 @@ -import { execSync } from "node:child_process"; +import { spawnSync } from "node:child_process"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; @@ -138,20 +138,14 @@ export function reconcile(options: ReconcileOptions): ReconcileResult { fs.mkdirSync(path.dirname(tempFile), { recursive: true }); fs.writeFileSync(tempFile, extractedCode, "utf8"); - try { - execSync(`node --check "${tempFile}"`, { stdio: "pipe" }); - log.audit("Syntax check passed"); - } catch (err) { - const stderr = - err && typeof err === "object" && "stderr" in err - ? String((err as { stderr?: Buffer }).stderr ?? "") - : err instanceof Error - ? err.message - : String(err); + const checkResult = spawnSync(process.execPath, ["--check", tempFile], { stdio: "pipe", encoding: "utf8" }); + if (checkResult.status !== 0 || checkResult.error) { + const stderr = checkResult.stderr ?? (checkResult.error ? checkResult.error.message : "Syntax check failed"); consolidateSharedContext(sharedContext, `Syntax Error:\n${stderr}`); if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); return { success: false, reason: "Syntax check failed" }; } + log.audit("Syntax check passed"); if (options.benchmark) { const staticResult = runBenchmarkStaticVerification(options.workspace, options.benchmark);