CodeVetter · Repo Unpack · Risk map
farion1231/cc-switch
The trust boundary is documented in SECURITY.md: the bundled renderer is trusted, but deep links, proxy listeners, sync payloads, imports, and upstream responses are not. The highest-impact code is the local proxy (optionally non-loopback), QuickJS usage scripts, and shell launches for terminals and session resume. Live config writes sit outside the repo and can brick Claude, Codex, Gemini, or Grok sessions if a takeover backup is mishandled. A few docs and defaults disagree with the code (i18n paths, Hermes visibility, AppType error text).
- SECURITY.md states there is no project cloud backend. The local HTTP proxy listen address is user-configurable and may be non-loopback; inbound requests are in scope. Also in scope: ccswitch:// payloads, WebDAV/S3 restores, SQL and config imports, proxy upstream responses, and credential leakage into logs or shared snippets. DevTools-only IPC calls are explicitly out of scope.
- CSP allows connect-src http: and https: plus ipc, and img-src http/https. script-src stays 'self' plus one hash. SECURITY.md says introducing eval, new Function, an iframe, or a wider script-src voids the renderer trust decision. launch_session_terminal documents that it forwards the renderer command string to a shell because of that trust decision, while cwd is shell-escaped.
- execute_usage_script evals attacker-influenceable JavaScript (deep link or imported provider) inside rquickjs. Mitigations present in code are a 5-second interrupt, 16 MiB memory cap, 256 KiB stack, and base_url validation except for custom templates that embed their own URL. The HTTP request itself is performed by Rust after the script returns a request object.
- open_provider_terminal extracts env vars, including API keys, from provider settings_config and passes them to launch_terminal_with_env. That function uses std::process::Command for bash, cmd, wsl.exe, osascript, and user-selected terminal binaries. MCP server commands are stored and later executed by the external CLI, not by validate_mcp_command, which only does a PATH lookup.
- macOS subscription quota reads credentials by spawning the security CLI. Device names for sync can come from CC_SWITCH_DEVICE_NAME or a hostname command. These are local process launches with the user's privileges.
- Provider API keys and live auth tokens are stored in SQLite providers.settings_config and in CLI files such as ~/.codex/auth.json. settings.json is written mode 0600 on Unix and secrets are stripped only in get_settings_for_frontend. Log redaction is exact-string based (minimum 8 characters) plus URL userinfo/query stripping, not a general secret scanner.
- Switching while a takeover backup exists must not overwrite the stub live file with the real upstream URL. recover_from_crash on the next launch is what restores live configs after a crash. A bug in hot_switch_provider_inner or the backup row can leave Claude/Codex/Gemini/Grok pointed at a dead local port or lose the pre-takeover file.
- AppType::from_str accepts mcode but the localized error string lists only claude, claude-desktop, codex, gemini, grokbuild, opencode, openclaw, hermes, and pi. Backend VisibleApps::default sets hermes to false while src/config/appConfig.tsx DEFAULT_VISIBLE_APPS sets hermes to true, so a missing settings value can disagree across the IPC boundary.
- CONTRIBUTING.md tells contributors to edit src/locales/*/translation.json and to run pnpm lint. Neither the path nor the script exists. Locale edits that follow the doc will not ship; the loaded files are src/i18n/locales/en.json, zh.json, zh-TW.json, and ja.json. package.json's description still names only Claude, Codex, and Gemini.
- The SQLite connection is a single Mutex<Connection>. Provider switches, MCP sync, and the 60-second session scanner all take that lock, sometimes inside spawn_blocking or block_on from async commands. Long writes (skill sync, usage rollup) stall every other command.
- God-module concentration: ProviderService::switch is in services/provider/mod.rs past line 5700, open_provider_terminal is in commands/misc.rs past line 3800, and proxy takeover lives in services/proxy.rs (a test at line 7571). A drive-by edit in those files can change every app's live write.
- Legacy compatibility still in the boot path: JSON config.json migration when no database exists, Windows HOME/.cc-switch fallback when the default db is missing, Claude settings.json versus claude.json, and deprecated get_mcp_config marked allow(deprecated). Official seed ids are special-cased so an import does not resurrect deleted official rows on ordinary startup, but a manual import does re-seed them.