From 53716220215fcc4ae5eb43a20bdbbff035924441 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Thu, 2 Apr 2026 04:07:36 -0700 Subject: [PATCH] fix(manager): validate flag tokens to prevent injection via config (#1410) Address review feedback: sanitize manager.flags values to allow only CLI-safe tokens (--flag patterns and alphanumeric values). Invalid tokens are dropped with a stderr warning. Prevents prompt injection via compromised config.json. Co-Authored-By: Claude Opus 4.6 (1M context) --- get-shit-done/bin/lib/init.cjs | 19 ++++++++++++++++--- tests/init-manager.test.cjs | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index dc0f8e03c..544413d25 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -1028,10 +1028,23 @@ function cmdInitManager(cwd, raw) { const completedCount = phases.filter(p => p.disk_status === 'complete').length; // Read manager flags from config (passthrough flags for each step) + // Validate: flags must be CLI-safe (only --flags, alphanumeric, hyphens, spaces) + const sanitizeFlags = (raw) => { + const val = typeof raw === 'string' ? raw : ''; + if (!val) return ''; + // Allow only --flag patterns with alphanumeric/hyphen values separated by spaces + const tokens = val.split(/\s+/).filter(Boolean); + const safe = tokens.every(t => /^--[a-zA-Z0-9][-a-zA-Z0-9]*$/.test(t) || /^[a-zA-Z0-9][-a-zA-Z0-9_.]*$/.test(t)); + if (!safe) { + process.stderr.write(`gsd-tools: warning: manager.flags contains invalid tokens, ignoring: ${val}\n`); + return ''; + } + return val; + }; const managerFlags = { - discuss: (config.manager && config.manager.flags && config.manager.flags.discuss) || '', - plan: (config.manager && config.manager.flags && config.manager.flags.plan) || '', - execute: (config.manager && config.manager.flags && config.manager.flags.execute) || '', + discuss: sanitizeFlags(config.manager && config.manager.flags && config.manager.flags.discuss), + plan: sanitizeFlags(config.manager && config.manager.flags && config.manager.flags.plan), + execute: sanitizeFlags(config.manager && config.manager.flags && config.manager.flags.execute), }; const result = { diff --git a/tests/init-manager.test.cjs b/tests/init-manager.test.cjs index 08d685ddb..ebdb508e7 100644 --- a/tests/init-manager.test.cjs +++ b/tests/init-manager.test.cjs @@ -453,4 +453,30 @@ describe('init manager', () => { assert.strictEqual(output.manager_flags.plan, '--skip-research'); assert.strictEqual(output.manager_flags.execute, '--interactive'); }); + + test('sanitizes invalid manager_flags to prevent injection (#1410)', () => { + writeState(tmpDir); + writeRoadmap(tmpDir, [{ number: '1', name: 'Test' }]); + + fs.writeFileSync( + path.join(tmpDir, '.planning', 'config.json'), + JSON.stringify({ + manager: { + flags: { + discuss: '; rm -rf /', + plan: '--valid-flag', + execute: '$(whoami)', + } + } + }) + ); + + const result = runGsdTools('init manager', tmpDir); + const output = JSON.parse(result.output); + + // Invalid flags should be sanitized to empty string + assert.strictEqual(output.manager_flags.discuss, '', 'injection attempt should be sanitized'); + assert.strictEqual(output.manager_flags.plan, '--valid-flag', 'valid flag should pass through'); + assert.strictEqual(output.manager_flags.execute, '', 'command substitution should be sanitized'); + }); });