From ade620795fc24d32aec834d81324c4af5e9bbae6 Mon Sep 17 00:00:00 2001 From: Lakshmikanthan K Date: Mon, 30 Mar 2026 18:35:08 +0530 Subject: [PATCH] fix(cli): prevent ZX_* control-plane injection via --env file loading a .env file via --env merges its contents into process.env, which resolveDefaults() then blindly promotes into live execution settings. An attacker who can modify the .env file (supply-chain PR, compromised dep) can set ZX_PREFIX/ZX_POSTFIX/ZX_SHELL to arbitrary shell code that runs for every call in an otherwise-trusted script. Fix: snapshot ZX_* keys present in process.env before dotenv.config() runs, then purge any ZX_* key introduced by the file before resolveDefaults() sees the environment. Legitimate ZX_* variables set by the operator (e.g. from the shell that launched zx) are preserved; only file-injected ones are stripped. Reported-by: LAKSHMIKANTHAN K (letchupkt) CWE: CWE-94 / CWE-77 (Command Injection) --- src/cli.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cli.ts b/src/cli.ts index df1ea719f0..b3b3a86c46 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -112,7 +112,19 @@ export async function main(): Promise { if (argv.cwd) $.cwd = argv.cwd if (argv.env) { const envfile = path.resolve($.cwd ?? process.cwd(), argv.env) + // Security: snapshot the ZX_* keys that existed BEFORE loading the env + // file so that attacker-controlled entries (e.g. ZX_PREFIX, ZX_POSTFIX, + // ZX_SHELL) cannot pollute the execution control-plane. + const zxKeysBefore = new Set( + Object.keys(process.env).filter((k) => k.startsWith('ZX_')) + ) dotenv.config(envfile) + // Purge any ZX_* key that was not present before the env-file was loaded. + for (const k of Object.keys(process.env)) { + if (k.startsWith('ZX_') && !zxKeysBefore.has(k)) { + delete process.env[k] + } + } resolveDefaults() } if (argv.verbose) $.verbose = true