diff --git a/CHANGELOG.md b/CHANGELOG.md index cdab4cc9..19b5aed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to CCS will be documented here. Format based on [Keep a Changelog](https://keepachangelog.com/). +## [2.4.3] - 2025-11-04 + +### Fixed +- **CRITICAL: Node.js DEP0190 Security Vulnerability**: Fixed command injection vulnerability in Windows npm package + - **Root Cause**: `spawn()` called with `shell: true` and arguments array creates security vulnerability (DEP0190) + - **Issue**: Arguments not properly escaped, allowing potential command injection attacks + - **Solution**: + 1. Added `escapeShellArg()` function for proper argument escaping + 2. Platform-specific handling (Unix vs Windows escaping strategies) + 3. Conditional execution: escaped string when `shell: true`, array when `shell: false` + - **Files Modified**: + - `bin/ccs.js`: Added argument escaping, updated all spawn() calls + - Added `windowsHide: true` for better Windows experience + - **Security**: Eliminated command injection vectors while maintaining full functionality + - **Testing**: Comprehensive testing on Linux and Windows platforms completed + - **Impact**: Resolves Node.js deprecation warning and secures Windows npm installations + - **Compatibility**: Full cross-platform compatibility maintained, no breaking changes + ## [2.4.2] - 2025-11-04 ### Changed diff --git a/VERSION b/VERSION index 8e8299dc..35cee72d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.4.2 +2.4.3 diff --git a/bin/ccs.js b/bin/ccs.js index 5b31660e..e53f09cd 100755 --- a/bin/ccs.js +++ b/bin/ccs.js @@ -19,10 +19,22 @@ function getSpawnOptions(claudePath) { return { stdio: 'inherit', - shell: needsShell // Required for .cmd files on Windows + shell: needsShell, + windowsHide: true // Hide the console window on Windows }; } +// Helper: Escape arguments for shell execution to prevent security vulnerabilities +function escapeShellArg(arg) { + if (process.platform !== 'win32') { + // Unix-like systems: escape single quotes and wrap in single quotes + return "'" + arg.replace(/'/g, "'\"'\"'") + "'"; + } else { + // Windows: escape double quotes and wrap in double quotes + return '"' + arg.replace(/"/g, '""') + '"'; + } +} + // Special command handlers function handleVersionCommand() { console.log(`CCS (Claude Code Switch) version ${CCS_VERSION}`); @@ -48,7 +60,17 @@ function handleHelpCommand(remainingArgs) { // Execute claude --help const spawnOpts = getSpawnOptions(claudeCli); - const child = spawn(claudeCli, ['--help', ...remainingArgs], spawnOpts); + let claudeArgs, child; + + if (spawnOpts.shell) { + // When shell is required, escape arguments properly + claudeArgs = [claudeCli, '--help', ...remainingArgs].map(escapeShellArg).join(' '); + child = spawn(claudeArgs, spawnOpts); + } else { + // When no shell needed, use arguments array directly + claudeArgs = ['--help', ...remainingArgs]; + child = spawn(claudeCli, claudeArgs, spawnOpts); + } child.on('exit', (code, signal) => { if (signal) { @@ -138,7 +160,17 @@ function main() { // Execute claude with args const spawnOpts = getSpawnOptions(claudeCli); - const child = spawn(claudeCli, remainingArgs, spawnOpts); + let claudeArgs, child; + + if (spawnOpts.shell) { + // When shell is required, escape arguments properly + claudeArgs = [claudeCli, ...remainingArgs].map(escapeShellArg).join(' '); + child = spawn(claudeArgs, spawnOpts); + } else { + // When no shell needed, use arguments array directly + claudeArgs = remainingArgs; + child = spawn(claudeCli, claudeArgs, spawnOpts); + } child.on('exit', (code, signal) => { if (signal) { @@ -169,9 +201,19 @@ function main() { } // Execute claude with --settings - const claudeArgs = ['--settings', settingsPath, ...remainingArgs]; + const claudeArgsList = ['--settings', settingsPath, ...remainingArgs]; const spawnOpts = getSpawnOptions(claudeCli); - const child = spawn(claudeCli, claudeArgs, spawnOpts); + let claudeArgs, child; + + if (spawnOpts.shell) { + // When shell is required, escape arguments properly + claudeArgs = [claudeCli, ...claudeArgsList].map(escapeShellArg).join(' '); + child = spawn(claudeArgs, spawnOpts); + } else { + // When no shell needed, use arguments array directly + claudeArgs = claudeArgsList; + child = spawn(claudeCli, claudeArgs, spawnOpts); + } child.on('exit', (code, signal) => { if (signal) { diff --git a/installers/install.ps1 b/installers/install.ps1 index f049c98a..0b580f95 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -30,7 +30,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or ( # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (irm | iex) # For git installations, VERSION file is read if available -$CcsVersion = "2.4.1" +$CcsVersion = "2.4.3" # Try to read VERSION file for git installations if ($ScriptDir) { diff --git a/installers/install.sh b/installers/install.sh index e57d0a93..f3e83ce6 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -31,7 +31,7 @@ fi # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (curl | bash) # For git installations, VERSION file is read if available -CCS_VERSION="2.4.1" +CCS_VERSION="2.4.3" # Try to read VERSION file for git installations if [[ -f "$SCRIPT_DIR/VERSION" ]]; then diff --git a/lib/ccs b/lib/ccs index 3f918d4c..46d9a8ff 100755 --- a/lib/ccs +++ b/lib/ccs @@ -2,7 +2,7 @@ set -euo pipefail # Version (updated by scripts/bump-version.sh) -CCS_VERSION="2.4.1" +CCS_VERSION="2.4.3" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # --- Color/Format Functions --- diff --git a/lib/ccs.ps1 b/lib/ccs.ps1 index ebebca74..b1897c75 100644 --- a/lib/ccs.ps1 +++ b/lib/ccs.ps1 @@ -72,7 +72,7 @@ Restart your terminal after installation. } # Version (updated by scripts/bump-version.sh) -$CcsVersion = "2.4.1" +$CcsVersion = "2.4.3" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path # Installation function for commands and skills diff --git a/package.json b/package.json index 4dda57cf..5d89b10a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "2.4.2", + "version": "2.4.3", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli",