diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 1446dff3..e56cddf2 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -12,3 +12,8 @@ **Vulnerability:** The redaction regex `KV_PATTERN` failed to match and redact quoted secret values (e.g., `password="mysecret"`), potentially leaking credentials in audit logs. **Learning:** Regular expressions for sanitizing key=value pairs must account for quoted values by explicitly including `"[^"]*"` and `'[^']*'` in the matching group. **Prevention:** When writing regex for secrets matching, always include patterns for both quoted and unquoted strings to prevent simple bypasses. + +## 2026-09-05 - Fix framework secrets leakage via execa extendEnv +**Vulnerability:** `execa` re-injects unsanitized `process.env` by default when `extendEnv` is true, causing framework secrets to leak into spawned processes. +**Learning:** Passing a sanitized environment object to `execa` is insufficient if `extendEnv: false` is not explicitly set, as `execa` merges the sanitized object with the unsanitized host `process.env`. +**Prevention:** Always explicitly set `extendEnv: false` when passing a sanitized environment object to `execa` in `src/core/tools/capability/runner.ts` and `src/cli/authorization/non-interactive.ts`. diff --git a/src/cli/authorization/non-interactive.ts b/src/cli/authorization/non-interactive.ts index 369e269b..ed339e5d 100644 --- a/src/cli/authorization/non-interactive.ts +++ b/src/cli/authorization/non-interactive.ts @@ -13,6 +13,7 @@ import { McpConnectionManager, } from '../../core/facades/cli-authorization-non-interactive.js'; import { isRecord } from '../../core/facades/cli-utils-serialize.js'; +import { sanitizeEnvironment } from '../../core/utils/sanitizer.js'; import { text } from '../locales/index.js'; const DecisionSchema = z @@ -100,6 +101,8 @@ export async function requestNonInteractiveAuthorizationDecision(params: { input: JSON.stringify({ request: params.request }), shell: true, timeout: timeoutMs, + env: sanitizeEnvironment(process.env), + extendEnv: false, reject: false, }); diff --git a/src/core/tools/capability/runner.ts b/src/core/tools/capability/runner.ts index dbba2080..91c71c9d 100644 --- a/src/core/tools/capability/runner.ts +++ b/src/core/tools/capability/runner.ts @@ -1,5 +1,7 @@ import { execa } from 'execa'; +import { sanitizeEnvironment } from '../../utils/sanitizer.js'; + import { ExecOpts, ExecResult } from './types.js'; /** @@ -15,7 +17,8 @@ export function createControlledRunner() { cwd: opts?.cwd, timeout: opts?.timeoutMs, maxBuffer: opts?.maxStdoutBytes, - env: opts?.env, + env: sanitizeEnvironment(opts?.env ? { ...process.env, ...opts.env } : process.env), + extendEnv: false, reject: false, // Backends should handle exit codes themselves });