-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(linux): add secretstorage to platform-critical packages (ACS-310) #1206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3e7ed40
6332566
2d7a0b7
a9831a9
6a709a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |||||||||
| import json | ||||||||||
| import logging | ||||||||||
| import os | ||||||||||
| import platform | ||||||||||
| import shutil | ||||||||||
| import subprocess | ||||||||||
| import threading | ||||||||||
|
|
@@ -24,11 +25,11 @@ | |||||||||
| from typing import Any | ||||||||||
|
|
||||||||||
| from core.platform import ( | ||||||||||
| get_claude_detection_paths_structured, | ||||||||||
| get_comspec_path, | ||||||||||
| is_macos, | ||||||||||
| is_windows, | ||||||||||
| validate_cli_path, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| logger = logging.getLogger(__name__) | ||||||||||
|
|
@@ -140,18 +141,83 @@ | |||||||||
| _CLI_CACHE_LOCK = threading.Lock() | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def _get_claude_detection_paths() -> dict[str, list[str] | str]: | ||||||||||
| def _get_claude_detection_paths() -> dict[str, list[str]]: | ||||||||||
| """ | ||||||||||
| Get all candidate paths for Claude CLI detection. | ||||||||||
|
|
||||||||||
| This is a thin wrapper around the platform module's implementation. | ||||||||||
| See core/platform/__init__.py:get_claude_detection_paths_structured() | ||||||||||
| for the canonical implementation. | ||||||||||
| Returns platform-specific paths where Claude CLI might be installed. | ||||||||||
|
|
||||||||||
| IMPORTANT: This function mirrors the frontend's getClaudeDetectionPaths() | ||||||||||
| in apps/frontend/src/main/cli-tool-manager.ts. Both implementations MUST | ||||||||||
| be kept in sync to ensure consistent detection behavior across the | ||||||||||
| Python backend and Electron frontend. | ||||||||||
|
|
||||||||||
| When adding new detection paths, update BOTH: | ||||||||||
| 1. This function (_get_claude_detection_paths in client.py) | ||||||||||
| 2. getClaudeDetectionPaths() in cli-tool-manager.ts | ||||||||||
|
|
||||||||||
| Returns: | ||||||||||
| Dict with 'homebrew', 'platform', and 'nvm_versions_dir' keys | ||||||||||
| Dict with 'homebrew', 'platform', and 'nvm' path lists | ||||||||||
| """ | ||||||||||
| return get_claude_detection_paths_structured() | ||||||||||
| home_dir = Path.home() | ||||||||||
| is_windows = platform.system() == "Windows" | ||||||||||
|
|
||||||||||
| homebrew_paths = [ | ||||||||||
| "/opt/homebrew/bin/claude", # Apple Silicon | ||||||||||
| "/usr/local/bin/claude", # Intel Mac | ||||||||||
| ] | ||||||||||
|
|
||||||||||
| if is_windows: | ||||||||||
| platform_paths = [ | ||||||||||
| str(home_dir / "AppData" / "Local" / "Programs" / "claude" / "claude.exe"), | ||||||||||
| str(home_dir / "AppData" / "Roaming" / "npm" / "claude.cmd"), | ||||||||||
| str(home_dir / ".local" / "bin" / "claude.exe"), | ||||||||||
| "C:\\Program Files\\Claude\\claude.exe", | ||||||||||
| "C:\\Program Files (x86)\\Claude\\claude.exe", | ||||||||||
| ] | ||||||||||
| else: | ||||||||||
| platform_paths = [ | ||||||||||
| str(home_dir / ".local" / "bin" / "claude"), | ||||||||||
| str(home_dir / "bin" / "claude"), | ||||||||||
| ] | ||||||||||
|
|
||||||||||
| nvm_versions_dir = str(home_dir / ".nvm" / "versions" / "node") | ||||||||||
|
|
||||||||||
| return { | ||||||||||
| "homebrew": homebrew_paths, | ||||||||||
| "platform": platform_paths, | ||||||||||
| "nvm_versions_dir": nvm_versions_dir, | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def _is_secure_path(path_str: str) -> bool: | ||||||||||
| """ | ||||||||||
| Validate that a path doesn't contain dangerous characters. | ||||||||||
|
|
||||||||||
| Prevents command injection attacks by rejecting paths with shell metacharacters, | ||||||||||
| directory traversal patterns, or environment variable expansion. | ||||||||||
|
|
||||||||||
| Args: | ||||||||||
| path_str: Path to validate | ||||||||||
|
|
||||||||||
| Returns: | ||||||||||
| True if the path is safe, False otherwise | ||||||||||
| """ | ||||||||||
| import re | ||||||||||
|
|
||||||||||
| dangerous_patterns = [ | ||||||||||
| r'[;&|`${}[\]<>!"^]', # Shell metacharacters | ||||||||||
| r"%[^%]+%", # Windows environment variable expansion | ||||||||||
| r"\.\./", # Unix directory traversal | ||||||||||
| r"\.\.\\", # Windows directory traversal | ||||||||||
| r"[\r\n]", # Newlines (command injection) | ||||||||||
| ] | ||||||||||
|
|
||||||||||
| for pattern in dangerous_patterns: | ||||||||||
| if re.search(pattern, path_str): | ||||||||||
| return False | ||||||||||
|
|
||||||||||
| return True | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def _validate_claude_cli(cli_path: str) -> tuple[bool, str | None]: | ||||||||||
|
|
@@ -174,11 +240,13 @@ | |||||||||
| import re | ||||||||||
|
|
||||||||||
| # Security validation: reject paths with shell metacharacters or directory traversal | ||||||||||
| if not validate_cli_path(cli_path): | ||||||||||
| if not _is_secure_path(cli_path): | ||||||||||
| logger.warning(f"Rejecting insecure Claude CLI path: {cli_path}") | ||||||||||
| return False, None | ||||||||||
|
|
||||||||||
| try: | ||||||||||
| is_windows = platform.system() == "Windows" | ||||||||||
|
|
||||||||||
| # Augment PATH with the CLI directory for proper resolution | ||||||||||
| env = os.environ.copy() | ||||||||||
| cli_dir = os.path.dirname(cli_path) | ||||||||||
|
|
@@ -189,9 +257,11 @@ | |||||||||
| # /d = disable AutoRun registry commands | ||||||||||
| # /s = strip first and last quotes, preserving inner quotes | ||||||||||
| # /c = run command then terminate | ||||||||||
| if is_windows() and cli_path.lower().endswith((".cmd", ".bat")): | ||||||||||
| # Get cmd.exe path from platform module | ||||||||||
| cmd_exe = get_comspec_path() | ||||||||||
| if is_windows and cli_path.lower().endswith((".cmd", ".bat")): | ||||||||||
| # Get cmd.exe path from environment or use default | ||||||||||
| cmd_exe = os.environ.get("ComSpec") or os.path.join( | ||||||||||
| os.environ.get("SystemRoot", "C:\\Windows"), "System32", "cmd.exe" | ||||||||||
| ) | ||||||||||
| # Use double-quoted command line for paths with spaces | ||||||||||
| cmd_line = f'""{cli_path}" --version"' | ||||||||||
| result = subprocess.run( | ||||||||||
|
|
@@ -209,7 +279,7 @@ | |||||||||
| text=True, | ||||||||||
| timeout=5, | ||||||||||
| env=env, | ||||||||||
| creationflags=subprocess.CREATE_NO_WINDOW if is_windows() else 0, | ||||||||||
| creationflags=subprocess.CREATE_NO_WINDOW if is_windows else 0, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| if result.returncode == 0: | ||||||||||
|
|
@@ -247,6 +317,7 @@ | |||||||||
| logger.debug(f"Using cached Claude CLI path: {cached}") | ||||||||||
| return cached | ||||||||||
|
|
||||||||||
| is_windows = platform.system() == "Windows" | ||||||||||
| paths = _get_claude_detection_paths() | ||||||||||
|
|
||||||||||
| # 1. Check environment variable override | ||||||||||
|
|
@@ -272,7 +343,7 @@ | |||||||||
| return which_path | ||||||||||
|
|
||||||||||
| # 3. Homebrew paths (macOS) | ||||||||||
| if is_macos(): | ||||||||||
| if platform.system() == "Darwin": | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Use This line uses ♻️ Proposed fix # 3. Homebrew paths (macOS)
- if platform.system() == "Darwin":
+ if is_macos():
for hb_path in paths["homebrew"]:📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| for hb_path in paths["homebrew"]: | ||||||||||
| if Path(hb_path).exists(): | ||||||||||
| valid, version = _validate_claude_cli(hb_path) | ||||||||||
|
|
@@ -283,7 +354,7 @@ | |||||||||
| return hb_path | ||||||||||
|
|
||||||||||
| # 4. NVM paths (Unix only) - check Node.js version manager installations | ||||||||||
| if not is_windows(): | ||||||||||
| if not is_windows: | ||||||||||
| nvm_dir = Path(paths["nvm_versions_dir"]) | ||||||||||
| if nvm_dir.exists(): | ||||||||||
| try: | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Inconsistent platform detection pattern - local variables shadow imported functions.
Multiple places create local
is_windowsvariables usingplatform.system() == "Windows", which shadows the importedis_windowsfunction fromcore.platform. This violates the coding guideline to use platform abstraction functions and creates confusion.♻️ Proposed fix
Use the imported
is_windows()function directly instead of creating local variables:def _get_claude_detection_paths() -> dict[str, list[str]]: ... home_dir = Path.home() - is_windows = platform.system() == "Windows" - - ... - - if is_windows: + if is_windows(): platform_paths = [Apply similar changes to
_validate_claude_cli()(line 248) andfind_claude_cli()(line 320).This also allows removing
import platformfrom line 19 if no other usages remain.As per coding guidelines: "Do not check
process.platformdirectly in code - always import platform detection functions from the platform abstraction module."Also applies to: 248-248, 320-320
🤖 Prompt for AI Agents