diff --git a/scripts/completion/ccs.bash b/scripts/completion/ccs.bash index 815f27a1..b9eb965e 100644 --- a/scripts/completion/ccs.bash +++ b/scripts/completion/ccs.bash @@ -156,7 +156,7 @@ _ccs_completion() { case "${prev}" in env) # Complete with profile names and flags (inline profiles since $cliproxy_profiles is out of scope) - local env_opts="--format --shell --help -h gemini codex agy qwen" + local env_opts="--format --shell --help -h gemini codex agy qwen iflow kiro ghcp claude" if [[ -f ~/.ccs/config.json ]]; then env_opts="$env_opts $(jq -r '.profiles | keys[]' ~/.ccs/config.json 2>/dev/null || true)" fi diff --git a/src/commands/env-command.ts b/src/commands/env-command.ts index 843b8c01..e461c32e 100644 --- a/src/commands/env-command.ts +++ b/src/commands/env-command.ts @@ -197,6 +197,11 @@ export async function handleEnvCommand(args: string[]): Promise { const format = formatStr as OutputFormat; const shellStr = parseFlag(args, 'shell') || 'auto'; + const validShellInputs = ['auto', 'bash', 'zsh', 'fish', 'powershell']; + if (!validShellInputs.includes(shellStr)) { + console.error(fail(`Invalid shell: ${shellStr}. Use: ${validShellInputs.join(', ')}`)); + process.exit(1); + } // zsh uses the same syntax as bash const shell = detectShell(shellStr === 'zsh' ? 'bash' : shellStr); diff --git a/tests/unit/commands/env-command.test.ts b/tests/unit/commands/env-command.test.ts index d70a140c..b6da6236 100644 --- a/tests/unit/commands/env-command.test.ts +++ b/tests/unit/commands/env-command.test.ts @@ -103,6 +103,12 @@ describe('env-command', () => { ); }); + it('prevents backtick injection in values', () => { + expect(formatExportLine('bash', 'TOKEN', 'safe`whoami`')).toBe( + "export TOKEN='safe`whoami`'" + ); + }); + it('escapes single quotes in fish values', () => { expect(formatExportLine('fish', 'VAL', "it's here")).toBe("set -gx VAL 'it'\\''s here'"); });