49 KiB
CC Switch v3.19.0
The through-line of this release is peace of mind: a concentrated wave of security hardening — Skill installation,
ccswitch://import confirmations, SQL backup imports, common-config merging, and terminal launching are all tightened, and two of those items are worth a minute of your attention — the Gemini common-config credential leak is fixed and scrubbed automatically after upgrading (you need to rotate your keys), and theccswitch://MCP import confirmation could previously fail to show the command it was about to write (if you have ever opened an import link of unknown origin, it is worth a check); both are covered under "Upgrade Notes". Alongside them is one major proxy correctness fix — reading images through the proxy no longer blows up your context (a single screenshot used to eat 100,000+ tokens, and two or three were enough to jam a Codex session on 400s). The convenience side is just as substantial: model pricing can be handed over to models.dev to maintain automatically, usage from Grok CLI's official login mode and the SuperGrok subscription balance finally reach the dashboard, and in-app updates now go through thedl.ccswitch.iomirror — so upgrading works even when GitHub is hard to reach.
Highlights: What You Can Do Now
- Read images through the proxy without blowing up your context: with Codex's
view_imageor any MCP tool that returns images, the image used to be serialized into tool text and token-counted as plain text (roughly 9,000× inflation); now every conversion bridge restores images to their native format before sending them upstream (files and audio are supported on the two Chat bridges as well). In a real test the same replayed turn dropped from 85k+ tokens to about 12k, with a 99% cache hit rate. - Hand model pricing over to models.dev: the usage panel gains "models.dev Automatic Pricing Sync" (off by default). Once enabled, the prices of the models you selected refresh automatically at startup (at most once every 6 hours); you can pick which models to track from the full catalog, or let it automatically include each vendor's latest commonly used models. From this release on, manual price edits and deletions are recorded in
~/.cc-switch/model-pricing.json, so they survive a database rebuild. - See usage and subscription balance for Grok's official mode: when Grok CLI signs in with official OAuth it cannot go through the local proxy, so that consumption was previously entirely invisible; now per-turn usage is imported from the session logs and presented in the dashboard as "Grok Build (Session)". Grok Build provider cards in the official category also show the SuperGrok subscription's quota usage and reset time directly.
- Open
ccswitch://import links with more confidence: the confirmation now shows the command, every argument, the URL, and the environment variables in full (credential-shaped values are shown masked), and highlights the values worth a second look — inline shell execution, environment variables that change loading behavior, intranet / metadata addresses; usage-query scripts show their complete code and are imported disabled by default. - Confirm that your Gemini providers no longer carry somebody else's key: the common-config shared snippet used to copy credentials such as
GOOGLE_API_KEYinto every Gemini provider that used it; this release closes that path, and the first launch after upgrading performs a one-time scrub automatically. Any key that ever entered the shared Gemini snippet should be treated as exposed — rotate it before re-entering it (see "Upgrade Notes"). - Keep updating the app when GitHub is hard to reach: the in-app updater queries
https://dl.ccswitch.io/latest.json(a Cloudflare R2 mirror) first, with GitHub as the fallback; minisign signature verification is unchanged, and the mirror itself is never trusted. - Use the latest models straight away when creating a provider: preset default models are upgraded to Claude Opus 5, GPT-5.6 Sol, and Gemini 3.6 Flash, with matching pricing seeded into the database; already-created providers are left as they are.
- Import fork-dense Codex usage history faster: each parent rollout file is parsed only once and shared across all of its fork points, so rebuilding fork-dense history speeds up noticeably, and the import results are byte-identical.
Usage Guides
The new capabilities in this release land mainly in the usage panel and ccswitch:// deep-link imports. The following docs are worth reading alongside it:
- Usage Statistics: the usage dashboard's data sources and how the statistics are counted. This release adds models.dev automatic pricing sync and usage import for Grok's official mode.
- Deep-Link Import (ccswitch://): what each field in the import confirmation means, and the default values of parameters such as
usageEnabled(from this release on, usage scripts are imported disabled by default, and the docs have been corrected to match). - Security Policy (SECURITY.md): this release fills in the threat model and reporting scope — which inputs are treated as untrusted and which issues are welcome, at a glance.
Warning
Only Official Channels (Please Read)
CC Switch is a fully free and open-source desktop app, and we do not charge users any fees. Please only obtain the software through the official channels listed below:
Channel Only Official Website ccswitch.io Source github.com/farion1231/cc-switch Downloads GitHub Releases Author @farion1231 Report an Imposter GitHub Issues Any "CC Switch" website or client that asks you for payment, top-ups, or login credentials is fake. If you have been tricked into paying, stop the transaction immediately and file a report through GitHub Issues.
Overview
CC Switch v3.19.0 is led by a wave of security hardening and one major proxy correctness fix. On the security side (#5811 plus follow-up standalone fixes): installing Skills from a GitHub repository is hardened against zip-slip and path traversal and given archive limits; the Gemini common-config credential leak is closed, and the first launch after upgrading performs a one-time scrub that cleans out keys already leaked into other providers' configs; importing a SQL backup now executes under a SQLite authorizer, and statements that can reach outside the importing database — ATTACH among them — are rejected outright; common-config snippet merging no longer follows __proto__ into the global prototype; external terminal launching switches to POSIX single-quote escaping, so a directory name can never inject a command again; and the ccswitch:// import confirmation displays the payload in full (credential-shaped values masked) and flags risky values, with usage scripts imported disabled by default. On the proxy side, images in tool results are no longer serialized into tool text but restored as native media on each conversion bridge (files and audio are supported on the two Chat bridges as well) — ending the "one 113 KB screenshot eats 100,000+ tokens, two or three images jam the Codex session on 400s" problem (#4465, #5663).
Usage statistics gain two new capabilities: models.dev automatic pricing sync (opt-in, #5734), together with persisting manual price edits / deletions to the human-editable ~/.cc-switch/model-pricing.json; and usage import for Grok CLI's official OAuth mode — traffic that cannot go through the local proxy and was previously entirely invisible — plus a SuperGrok subscription quota display on the provider card. Around distribution and experience: in-app updates prefer the Cloudflare R2 mirror at dl.ccswitch.io (GitHub as fallback, signature verification unchanged); Codex usage import reuses an already-parsed parent rollout timeline for forked sessions (#5626); preset default models move up to Claude Opus 5, GPT-5.6 Sol, and Gemini 3.6 Flash; the OpenClaw Kimi For Coding preset's base URL is corrected; and the toolbar app switcher becomes icon-only. This release has no database schema migration, so upgrading is lightweight.
Release date: 2026-07-30
Stats: 38 commits | 132 files changed | +14,926 / -1,415 lines
Added
models.dev Automatic Pricing Sync
The pricing area of the usage panel gains a "models.dev Automatic Pricing Sync" card, off by default and enabled manually: turning it on shows a confirmation explaining that CC Switch will refresh the prices of the selected models from models.dev at startup (at most once every 6 hours), and that both the built-in price and a hand-set price for a matching model will be overwritten. The "Select Models" dialog offers the full models.dev catalog (searchable and filterable), plus an "automatically include common models" option covering the recently released models from Claude, GPT, Gemini, Grok, DeepSeek, Qwen, MiMo, LongCat, Kimi, MiniMax, and GLM (at most 6 per family, each individually excludable). The card shows the last sync time and any errors, offers "Sync Now", and can open or reload the local pricing file.
From this release on, manual price edits and deletions are also recorded in ~/.cc-switch/model-pricing.json, a human-editable file sitting next to the database, and replayed on every startup — hand-set pricing no longer disappears after a database rebuild, and a deleted built-in price finally stays deleted (recorded as a tombstone rather than being re-seeded). Note that the file starts empty and is deliberately not back-filled from the existing pricing table (otherwise built-in prices would all be written in as overrides, blocking future built-in price corrections), so edits made before the upgrade still live only in the database — re-save one and it enters the file. When a sync genuinely changes a price, historical usage rows whose cost was never computed (zero or missing) are recalculated at the new price — rows that already have a cost keep their original values; a failed fetch or being offline never blocks startup. The models.dev list also filters out non-text and deprecated models (audio / image / video / embedding, and so on), which tidies up the manual price-selection dialog along the way. (#5734)
Grok Official Mode Usage Finally Reaches the Dashboard
When Grok CLI signs in with official OAuth it cannot be routed through the local proxy — Grok uses an empty config as the mode switch, so there is nowhere to point it at CC Switch — and that consumption was previously entirely invisible in the usage dashboard. CC Switch now imports per-turn usage alongside the regular session-log sync, reading turn_completed events from updates.jsonl under ~/.grok/sessions (archived sessions included): cost prefers the exact figure the CLI reports itself, falling back to local pricing when that is missing (the built-in pricing table gains grok-4.5-build at $2 input / $6 output / $0.30 cache read, per million tokens). Imported rows are keyed on the upstream per-turn ID, so a rolled-back session cannot cause double counting; a settling window plus a recent-proxy-activity check ensures the same traffic is not counted twice when routed and official modes are mixed. New rows appear in the dashboard under the provider name "Grok Build (Session)", the app filter gains a Grok Build option, and the data-source breakdown gains a "Grok Build Session" entry with its own icon — all four locales included.
SuperGrok Subscription Quota on Provider Cards
Grok Build providers in the "official" category now show SuperGrok subscription usage directly on the card, alongside the official-subscription footers for Claude Code / Codex / Gemini: CC Switch reads Grok CLI's own OAuth credentials (~/.grok/auth.json) and queries grok.com's billing endpoint for the quota window's used percentage and reset time; when the reset interval is recognizable it is labeled "weekly" or "monthly", otherwise it falls into a new "Credits" tier (rendered as the c group in the tray usage summary). A transient network failure keeps the previous reading and retries rather than clearing the footer; an expired token prompts you to run grok login again. Managed xAI OAuth (SuperGrok) providers in Claude Code, Claude Desktop, and Codex automatically get the same quota display — the data comes from the account bound to that provider, and the usage-script entry is hidden accordingly. Note that whether a Grok Build provider counts as "official" is now decided purely by the category field, with no probing of config contents.
Claude Opus 5 Built-In Pricing
claude-opus-5 joins the built-in pricing table at $5 input / $25 output, $0.50 cache read / $6.25 cache write (per million tokens), so its usage no longer shows $0. It is seeded insert-if-absent, so edited prices are unaffected (Opus 5 fast mode bills separately and is deliberately left out of the table).
Preset Catalog Updates
A6API (an aggregation platform that automatically picks the best of several upstreams for the same model) joins the sponsor presets across eight apps; the PackyCode preset gains three backup addresses on the five preset types that support backup endpoints (Claude Code / Claude Desktop / Codex / Gemini CLI / Grok Build), selectable in the endpoint manager and the speed test; the AICoding partner preset returns across seven apps; and sponsor ordering is realigned with the README.
Changed
Preset Default Models Upgraded: Claude Opus 5, GPT-5.6 Sol, Gemini 3.6 Flash
The built-in presets' default models are all on the current generation now: claude-opus-5 replaces claude-opus-4-8 (all three naming forms covered), gpt-5.6-sol replaces gpt-5.5 and bare gpt-5.6, and gemini-3.6-flash replaces gemini-3.5-flash. Every mirrored location was updated in sync — universal / NewAPI defaults, the Codex custom config.toml template, recommendation lists, form placeholders, and copy in all four locales; gemini-3.6-flash pricing is seeded into the database at the same time ($1.50 / $7.50, $0.15 cache read, per million tokens). The Code0 and Qiniu Gemini presets, still pinned to gemini-3.1-pro-preview, are aligned to 3.6 Flash as well — this is a deliberate tier change: 3.6 has no Pro version, and 3.5 Pro is still restricted to partner testing. Defaults only affect newly created providers; saved providers keep the model they were created with. Claude Desktop's opus route advances to claude-opus-5, with claude-opus-4-8 moving into the compatibility-alias slot, so existing configs still resolve as before.
In-App Updates Now Go Through the ccswitch.io Mirror
The updater now queries https://dl.ccswitch.io/latest.json first — a Cloudflare R2 mirror of the release manifest — with GitHub Releases as the fallback, so checking for and downloading updates no longer depends on GitHub being reachable. The mirror manifest points every platform's download at the same bucket, while the minisign signatures stay exactly as they were: a signature covers file content, not the URL, and every downloaded artifact is still verified against the built-in public key, so the mirror itself is never trusted. Publishing is handled by a release-gated sync workflow that only rewrites the root manifest when the tag really is GitHub's releases/latest, so the mirror can never push users back to an older version.
Codex Usage Import: Faster on Forked Sessions
Importing and rebuilding Codex usage statistics no longer re-reads the same parent rollout file once per fork point: each parent ~/.codex/sessions/*.jsonl is parsed only once into an in-memory token timeline shared by every child session forked from it, and each child's cutoff becomes an in-memory filter instead. The cache is validated by a file identity stamp (modification time, size, plus device/inode on Unix or volume serial number + file ID on Windows), so a parent file that was appended to, rotated, or replaced is re-read rather than served stale. How much faster it gets depends on fork density: fork-dense history sheds a great deal of redundant parsing, while fork-sparse history is essentially unchanged — and in both cases the import results are byte-identical. (#5626)
Toolbar App Switcher Becomes Icon-Only
The switcher buttons no longer render a text label next to the icon — with the managed apps grown to eight, the labels were already being collapsed by overflow detection almost all the time, so the ResizeObserver-based auto-compaction was removed and only the icons are shown. App names remain in the hover tooltip, and screen readers still reach them through aria-label.
Sponsor Domains and Referral Links Refreshed
Several sponsors migrated domains, and preset addresses, backup endpoints, referral links, and README rows have been updated to match (PackyCode → www.packyapi.ai, RightCode → www.rightapi.ai, ClaudeAPI → www.apito.ai, APINebula → apinebula.ai, AICodeMirror → .ai, AICoding → .inc, AIGoCode → .app), along with the removal of two backup endpoints that had gone dead. Already-created providers keep the old address stored in the database — to move to the new domain, edit the address by hand or recreate the provider from the refreshed preset.
Fixed
Reading Images Through the Proxy No Longer Blows Up the Context
When a client read an image through a tool call — Codex's view_image, or any MCP tool that returns images — the proxy's protocol conversion serialized the entire image block into the tool message's text, and the upstream token-counted the base64 as plain text: roughly 9,000× inflation, with a 113 KB PNG working out to 100,000+ prompt tokens; Codex replays the whole history every turn, so two or three screenshots were enough to push the session out of the context window and jam it on repeated 400s (#4465, #5663).
The proxy now lifts media payloads out of tool results and re-sends them in each bridge's native format — images are covered on every bridge; files and audio apply wherever the target protocol supports them: the two Chat bridges (Claude→Chat, Codex Responses→Chat) carry images / files / audio, leaving a short marker in the tool message with the media following the tool batch as a synthetic user message; Claude→Responses restores native input_image; Codex / GrokBuild→Anthropic rebuilds standard Anthropic image blocks; and Claude→Gemini uses multimodal functionResponse.parts on Gemini 3 (inlineData on older models), accepting inline base64 images only. Detection covers typed Responses blocks, Anthropic source blocks, MCP data+mimeType results, and whole image data URLs, and sees through arrays and nested content wrappers (including JSON-encoded tool output); once an output is judged to contain media, any data URLs and bare base64 left in it are collapsed into placeholders — bare base64 on its own never triggers media detection, and ordinary tool output is left exactly as it was. Tool results without media stay byte-identical to before on every bridge, so prompt-cache prefixes are unaffected; the media blocks sent upstream deliberately carry no cache_control marker, so strict upstreams such as GLM and Qwen do not reject them. Measured end to end against Kimi K3: the same replayed turn settles at about 12k input tokens with a 99% cache hit rate, where each replay previously had to carry 85k+ of base64 text.
"Unsupported Image Fallback" Now Sees Images Inside Tool Results
The "unsupported image fallback" setting replaces image blocks with a placeholder marker when a provider is text-only or the upstream rejects images, but it could previously only see images that were still structured blocks — tool-result images already flattened into base64 text were invisible to it, so a text-only upstream simply failed with no way to recover. The media scrubber now symmetrically detects and strips media inside tool output on every path, so both the strip-before-sending route and the retry-after-rejection route can rescue such turns. Because that detection now also reaches into tool results, a regression test pins down that the reactive retry still fires only on genuine modality rejections — a context-length 400 is not mistaken for an image rejection and retried.
Grok Build Cost Backfill No Longer Overestimates
The routine that backfills missing costs previously treated only Codex and Gemini as providers whose reported input tokens already include cache reads, while Grok Build follows the same convention — so backfilled Grok Build rows were priced on the full input and then charged for the cache read a second time, inflating the cost. The set of cache-inclusive providers is now defined in exactly one place, shared by the route logger, the cost calculator, and the backfill routine, so the three can no longer disagree. Note that rows already touched by the old backfill keep their values — the backfill only handles zero-cost rows and never rewrites an existing positive cost.
Hand-Edited Config Files No Longer Crash the App or Swallow Your Edits
When mcp_servers exists in ~/.codex/config.toml but is not a table (for example mcp_servers = "x"), MCP sync panicked mid-switch — and it happened after both the database and the live config had already been written, leaving a half-applied switch; a non-table value is now warned about and normalized to an empty table, with the same fix in the Codex and GrokBuild writers. The inline-table form (valid TOML) had mirror-image problems: MCP deletion silently did nothing while the UI reported success, and base_url edits were written to a level Codex never reads — both handled. An opencode.json whose root node or provider / mcp section is an array or scalar no longer panics; such files are rejected with an error instead of being rebuilt, so your own model and theme settings are not wiped out. (#5811)
Proxy Conversion Survives Malformed Upstream Responses
Malformed data from an upstream gateway could previously take down the local proxy outright instead of producing an error: a non-object message or content_block in an Anthropic SSE stream, and a buffered response body that is a top-level JSON array or scalar (what a gateway that ignores stream: true returns), both hit a panicking indexed assignment; the stream now ends with a normal failure event. A malformed content_block header is additionally recovered as a text block — merely sanitizing it into an empty object stops the panic but leaves all the following content silently discarded, making the model look like it said nothing — and since the deltas after a bad header are usually intact, the common case now passes through, with a warning logged whenever the substitution happens. (#5811)
OpenClaw's Kimi For Coding Address Corrected
The OpenClaw preset previously pointed at the general platform endpoint https://api.kimi.com/v1, which is not what the Kimi For Coding subscription uses, so coding-plan keys did not work. The address is corrected to https://api.kimi.com/coding/v1, with the form placeholder and default value updated to match. Providers created from the old preset need to be pointed at the new address manually.
Security Hardening
Of the nine items in this section, two need action from you: rotating any key that entered the Gemini common config, and checking the MCP entries you once imported through ccswitch:// — "Upgrade Notes" spells out how. The rest take effect on upgrade with nothing for you to do.
If you never open ccswitch:// links sent to you by other people and have never used a shared Gemini common config, these nine items mostly mean "less can go wrong from here on"; if either of those two applies to you, this release is worth upgrading to first.
The Gemini Common Config No Longer Leaks Keys, and Scrubs Automatically After Upgrading
The Gemini common-config extractor previously stripped only GEMINI_API_KEY and GOOGLE_GEMINI_BASE_URL from the shared snippet and copied every other env entry verbatim — but GOOGLE_API_KEY is a first-class Gemini credential, so one account's key (along with any other credential-looking entries) was deep-merged into every Gemini provider that used the common config and sent to that provider's base URL, which may well be a third-party relay. The extractor now skips every key that matches a credential pattern (the same matcher set as the Claude extractor), the frontend snippet validator is aligned with it, and a hand edit cannot push one back in either. Because a Gemini snippet is never re-extracted once it exists, the first launch after upgrading also performs a one-time scrub: leaked credentials are cleared from the snippet, from every provider they were merged into, and from ~/.gemini/.env — matched on the key name plus an exact value match, so a provider's own same-named key with a different value is not caught up in it — while the env file's formatting and comments are preserved. See "Upgrade Notes" for the scrub's details and caveats. (#5811)
Skill Repository Installation Hardened: Path Traversal and Archive Limits
Installing or browsing Skills from a GitHub repository could previously write outside the target directory: archive entries were joined onto the destination path without normalization, so a ZIP containing .. could escape the extraction directory (zip-slip); and repository coordinates were never validated, so a branch name like ../../../releases/download/v1/evil could redirect the download to an arbitrary release asset — while Skill repositories can be added through an untrusted ccswitch:// deep link and are enabled by default, so merely opening the Skills panel is enough to trigger a download. Skill directory values coming from backup restores, sync snapshots, and "import from app" were likewise joined into paths without validation, so an uninstall could remove_dir_all outside the managed directory. Every landing point now validates the directory name, repository owner / name / branch are allowlisted at the single download choke point, extraction has hard limits (10,000 entries, 512 MB written, 128 MB downloaded, 4 KB symlink targets, with self-referential links rejected), and the new error messages ship in all four locales. (#5811)
Deep-Link Import Confirmation: See Everything, Flag the Risks
The ccswitch:// MCP import confirmation previously rendered only a single truncated Command: line and showed nothing of args, url, or env — a link carrying command: "sh" plus args: ["-c", "curl …|sh"] and an LD_PRELOAD environment variable displayed as nothing more than an innocuous sh, yet on confirmation it was written into each app's live MCP file. The confirmation now renders the command, every argument, the URL, and the environment variables line by line, wrapping instead of truncating, so nothing gets clipped out of sight (env values whose key name contains TOKEN / KEY / SECRET / PASSWORD are shown masked, as a prefix plus asterisks). Values worth a second look are highlighted and collected into a warning block: shell interpreters carrying an inline-execution flag (including combined forms such as bash -lc, cmd /C, and PowerShell's -Command abbreviations), environment variables that change process loading behavior (LD_*, DYLD_*, NODE_OPTIONS, PYTHONPATH, PATH, proxy variables, and so on), and endpoints pointing at loopback / intranet / cloud metadata addresses. Flagging is purely advisory and never blocks an import — a local Ollama endpoint is a perfectly ordinary thing to have. The provider confirmation gets the same treatment, and the "will be written to all specified apps immediately" warning is now shown unconditionally instead of being gated on link-controlled fields.
Deep-Link Usage Scripts: Disabled by Default, Code Shown Before Use
A usage-query script imported through a deep link is JavaScript that runs every time usage is queried, and it could previously be enabled without you ever having seen the code: the backend treated "code was supplied" as "consent to execute", and the confirmation showed only an enabled / disabled badge, never the script body. Scripts are now disabled by default — a link must explicitly carry usageEnabled=true to request enabling — and the confirmation shows the entire decoded script in a scrollable, fully wrapped code block, warning that it will be executed once enabled. If decoding fails it falls back to showing the raw payload, so a malformed script cannot masquerade as "no script". The script code is still stored on the provider, and you can enable it manually inside the app after reviewing it.
URL-Safe Base64 Could Blank the Whole Confirmation Dialog
The two items above fix "the confirmation doesn't show enough"; this one fixes "the confirmation can show nothing at all". The backend accepts four Base64 variants (including RFC 4648 §5's URL-safe alphabet), while the frontend's atob only knows the standard alphabet and, when it cannot decode, returns the input unchanged instead of raising an error — so for one and the same payload, the backend decodes successfully and imports, while the frontend is left holding a blob of undecodable characters. Usage scripts and system prompts therefore displayed as opaque Base64; MCP configs were the worst: the JSON.parse failure was swallowed by the component, the confirmation rendered "0 servers" with an empty list, and the backend went right on writing the real entries into the live MCP file. Changing a single / to _ in the payload was enough — the confirmation goes blank, the import still works, and the full display the two items above had just added is defeated along with it.
The frontend decoder now normalizes the URL-safe alphabet before decoding, so what the confirmation shows is always what will be imported; the shared decoder gains unit tests for the first time, containing a precondition self-assertion that the sample really does land on the URL-safe branch rather than happening to be identical in both encodings.
This defect affects every version since v3.8.0. If you have ever imported MCP servers through a
ccswitch://link, it is worth a check — see "Upgrade Notes".
SQL Imports Reject Statements That Reach Outside the Importing Database
Importing a database backup previously validated only the file's header comment and then handed the whole text straight to execute_batch — a carefully crafted backup could ATTACH DATABASE to create a SQLite file anywhere the user can write, and that side effect happened before the import's own state validation, so the file landed even when the import failed as a whole; WebDAV / S3 sync snapshots go through the same code path. A SQLite authorizer is now installed for the duration of an external batch (and removed the moment it ends, so the app's own schema maintenance is unaffected): ATTACH / DETACH, VACUUM, virtual-table creation (file-backed modules such as csvfile can read and write arbitrary paths), and every action SQLite reports as unknown are all rejected — future new statements fail by default; of the PRAGMAs, only the two the exporter actually writes, foreign_keys and user_version, are allowed through.
Prototype Pollution in Common-Config Snippets
The three walkers that apply, remove, and compare common-config snippets all used to follow __proto__ into the global Object.prototype: JSON.parse('{"__proto__":{…}}') yields an own enumerable property, so merging wrote attacker-chosen values onto the global prototype — and the settings table is overwritten wholesale from the remote during sync, so once a malicious WebDAV / S3 snapshot has landed, opening a provider form once is enough to trigger the merge. All three walkers now skip __proto__, constructor, and prototype; the "common config applied" comparison additionally requires own properties, which incidentally fixes a visible oddity — {"__proto__":{}} used to be judged a subset of any config.
Command Injection Through Directory Names When Launching a Terminal
When resuming a session in an external terminal, the cd line previously wrapped the working directory in double quotes and escaped only backslashes and double quotes — but inside double quotes a shell still expands $(…), backticks, and $VAR, and this value is the real project path recorded in the CLI's session history, where on macOS a directory name may legitimately contain those characters. Name a folder that way, and clicking "Resume" executes the embedded command in your terminal, with no compromised component involved anywhere. The three launchers that assemble a shell line — Terminal.app, iTerm, and kitty — switch to POSIX single-quote escaping, under which nothing expands at all (the AppleScript quoting layer needed to get through Terminal / iTerm is handled safely too); Ghostty, WezTerm / Kaku, and Alacritty already passed the directory as a separate argument and were safe to begin with.
GrokBuild Credential Resolution No Longer Substitutes or Inlines Environment Keys
GrokBuild credential extraction previously fell back to the process-level XAI_API_KEY when the env_key variable named in the config was unset — silently substituting another account's key and sending it to whatever base URL the config pointed at; credentials now come only from an explicit inline api_key or from the environment variable that env_key names exactly. Deep-link imports no longer resolve environment variables into a plaintext api_key; a link carrying only an env_key name is rejected with a prompt to add it manually — accepting it as-is would mean the victim's environment key still gets resolved at request time and sent to the address the link declares. Fixed along the way: base URL resolution is decoupled from credential resolution, where previously a missing credential cleared the base URL along with it, so on macOS (where GUI processes do not inherit the shell environment) the address shown in the UI differed from the one actually used, and the usage script's {{baseUrl}} expanded to empty. (#5811)
Documentation
"Using GPT Models in Claude Code" Guide Completed in English and Japanese
The local routing guide that previously existed only in Chinese has now been fully ported to English and Japanese, covering both connection paths end to end: a third-party OpenAI Responses gateway (API key), and a ChatGPT Plus/Pro subscription through Codex device-code OAuth sign-in. Both routing guides were also retitled around "which model" rather than "which client pairs with which" — Using GPT Models in Claude Code and Using Claude Models in Codex — and every cross-link (including the v3.18.0 release notes in all three languages) now points at the version in the reader's own language.
User Manual: Deep-Link usageEnabled Default Corrected
The deep-link reference in the three-language user manual previously claimed usageEnabled defaults to true, when the actual default is false, matching the importer. The manual now states the correct default and adds two consequences: the confirmation shows the script's full code before import, and without an explicit usageEnabled=true the script is imported disabled and can be enabled inside the app later.
SECURITY.md: Threat Model and Reporting Scope
SECURITY.md gains a bilingual threat model and an explicit in-scope / out-of-scope list, triaging reports by "who controls this input" rather than "which API the value eventually reached": the built-in WebView renderer is declared a trusted component (with four independently verifiable facts and the conditions that would invalidate them); deep-link payloads, WebDAV / S3 restore data, imported files, upstream API responses, and inbound requests to the local proxy are all listed as untrusted inputs, and reports about them are welcome.
Upgrade Notes
No Database Migration in This Release
v3.19.0 contains no schema migration (the version stays at v16), so it is ready to use straight after upgrading, with no wait for a data rebuild.
One-Time Gemini Key Scrub (Please Read)
The first launch after upgrading performs a one-time Gemini common-config scrub before the regular config extraction. Some Gemini providers may afterwards report a missing API key: entries are deleted by matching a credential-style key name plus an exact value match, and what usually gets removed is another provider's credential that leaked in through the shared snippet (that provider's own original value was already overwritten when the leak happened and cannot be recovered) — but a same-value key you intentionally reused across several Gemini providers is removed too. Either way, rotate before re-entering: any key that ever entered the shared Gemini snippet should be treated as exposed. The deleted key names and the affected provider ids (never the values) are recorded in the settings table under gemini_common_config_scrub_audit_v1, which you can use to locate each provider whose key needs filling in again.
Ever Imported MCP Through a Deep Link? Worth a Check (Please Read)
Before this release, the ccswitch:// MCP import confirmation could fail to show what was about to be written: arguments and environment variables were not rendered at all (command: "sh" plus args: ["-c", …] displayed as a harmless sh), and if the payload was URL-safe Base64 encoded the whole list displayed as "0 servers" — while in both cases the backend went right on writing the entries into each app's live MCP file. Both defects affect every version since v3.8.0 and are fixed together in this release.
Exploiting this requires you to personally open an attacker-supplied link and click "Import", so the vast majority of users are unaffected. If you have in fact opened a ccswitch:// MCP import link from a source you did not fully trust, it is worth going through the MCP panel entry by entry, or checking mcpServers in ~/.claude.json directly (for Codex, mcp_servers in ~/.codex/config.toml), to confirm there is nothing there you do not recognize — MCP servers are executed as child processes the next time the CLI starts.
Deep-Link Usage Scripts Are Disabled by Default
A deep link carrying a usage-query script is now imported with the script disabled, unless the link explicitly carries usageEnabled=true. Links that relied on automatic enabling (some partners' one-click setup links, for example) will import the script without turning usage queries on — review the code and enable it manually in the provider editor. Usage scripts configured by hand inside the app are unaffected.
New Default Models Only Affect Newly Created Providers
Saved providers keep the model ID they were created with; using the new models requires editing them by hand. Claude Desktop's opus route advances to claude-opus-5 and claude-opus-4-8 moves into the compatibility-alias slot, so existing configs still resolve as before.
Pricing Seeding and the Local Pricing File
The new pricing rows (claude-opus-5, gemini-3.6-flash, grok-4.5-build) are appended on next launch via insert-if-absent — seeding never overwrites a price you have edited. ~/.cc-switch/model-pricing.json starts empty and records only the manual price edits and deletions made from this release on — earlier edits are not migrated into it; to make them survive a database rebuild, just re-save them once. The models.dev automatic sync stays off until you turn it on; once it is on, it is the one path that overwrites matching prices, built-in and hand-edited alike.
GrokBuild Implicit Environment-Variable Fallback Removed
GrokBuild providers that relied on the implicit XAI_API_KEY environment fallback now need an explicit api_key or a correctly named env_key.
Grok Official Mode Usage Is Intentionally Delayed
Grok usage from official mode appears after a delay of roughly ten minutes plus one sync cycle — events settle first and are then checked against proxy-recorded rows to prevent double counting; if routed traffic and official traffic alternate within the window, some official turns are skipped rather than risking a duplicate count. Grok Build rows that the old cost backfill overestimated keep their values — the backfill only handles zero-cost rows and never revises an existing positive cost.
The Update Mirror Takes Effect from the Next Release
The updater's endpoint list is baked into the app binary, so existing installations still query only GitHub until they upgrade to a version containing this change; from then on the dl.ccswitch.io mirror comes first, with GitHub as fallback.
Sponsor Domain Migrations Do Not Change Existing Providers
Already-created providers keep the old address stored in the database and still point at the old domain. To move to the new domain, edit the provider's address by hand, or recreate it from the refreshed preset.
Risk Notice
SuperGrok Quota Queries (New in This Release)
The SuperGrok quota display on provider cards reads Grok CLI's own OAuth credentials (~/.grok/auth.json) and queries grok.com's billing endpoint — an endpoint that is not publicly documented, and whose response parsing is based on observation of the current format, so this feature may stop working once xAI changes the interface (at which point the card degrades to showing no quota, with nothing else affected). CC Switch neither stores nor modifies these credentials.
Carried-Over Notices
xAI Grok OAuth sign-in: reuses the public OAuth client identity of the official Grok CLI; using it could lead to account restriction or suspension — see the v3.18.0 release notes for details.
Codex OAuth reverse proxy: using a ChatGPT subscription's Codex OAuth through a reverse proxy may violate OpenAI's terms of service. See the v3.13.0 release notes for details.
Third-party provider routing: when CC Switch's local proxy converts and forwards Codex, Claude Desktop, or Grok Build requests to third-party providers, each provider may have different requirements for billing, compliance, and data retention. Read the target provider's terms before use.
By enabling these features, users accept the related risks. CC Switch is not responsible for account restrictions, warnings, or service suspensions caused by using these features.
Thanks
This release's security hardening came almost entirely from outside — one PR, plus the security reports we received.
Code Contributions
- #5811: zip-slip and repository-coordinate traversal in Skill installation, the Gemini common-config credential leak and its one-time scrub, GrokBuild credential resolution, and fixes across several panic paths, thanks @zayokami. This is the broadest single body of work in the release.
- #5734: models.dev automatic pricing sync, thanks @YUZHEthefool.
- #5626: faster Codex usage import for forked sessions, thanks @ayanamislover (co-credited with @SaladDay).
Security Reports
Four of the fixes in this release's "Security Hardening" section came from security reports sent privately. Thanks to 23pds (SlowMist) and zues devil — attributed item by item below:
- The deep-link import confirmation showed only a single truncated
Command:line —args,url, andenvwere not rendered at all, so ansh -cpayload withLD_PRELOADlooked like nothing but anshin the UI. This is the highest-impact item in the release. (23pds, SlowMist) - SQL backup imports were unconstrained —
ATTACH DATABASEcould create a file anywhere the user can write, and the side effect happened before the import's own validation. (zues devil) - Command injection through directory names when launching an external terminal — the
cdline was double-quoted, so$(…)still expanded, and this value is the real project path recorded in the session history. (Reported independently by zues devil and 23pds, against the built-in launchers and the custom template paths respectively.) - Prototype pollution in common-config snippet merging — all three walkers followed
__proto__into the globalObject.prototype. (23pds, SlowMist)
These reports also prompted us to fill in the threat model and reporting scope in SECURITY.md — until then, this project documented only how to report, never what counts as a vulnerability.
The other two deep-link fixes (usage scripts disabled by default, and the URL-safe Base64 bypass) were found while reviewing those fixes themselves, and were not part of the original reports.
Issue Reports
Thanks to the users who reported proxy image reads blowing up the context in #4465 and #5663 — this release's most important proxy fix came from the reproduction clues in those real-world scenarios.
Download & Install
Visit Releases and download the build for your system, or get it from the official site ccswitch.io (from this release on, downloads are distributed through Cloudflare edge nodes and no longer depend on GitHub being reachable).
System Requirements
| System | Minimum Version | Architecture |
|---|---|---|
| Windows | Windows 10 and later | x64 / ARM64 |
| macOS | macOS 12 (Monterey)+ | Intel (x64) / Apple Silicon (arm64) |
| Linux | See table below | x64 / ARM64 |
Windows
| File | Description |
|---|---|
CC-Switch-v3.19.0-Windows.msi |
Recommended - MSI installer with auto-update |
CC-Switch-v3.19.0-Windows-Portable.zip |
Portable build, unzip and run |
Windows ARM64 devices should pick the artifact whose file name carries the arm64 tag.
macOS
| File | Description |
|---|---|
CC-Switch-v3.19.0-macOS.dmg |
Recommended - DMG installer, drag to Applications |
CC-Switch-v3.19.0-macOS.zip |
Unzip and drag to Applications, Universal Binary |
CC-Switch-v3.19.0-macOS.tar.gz |
For Homebrew install and auto-update |
Homebrew install:
brew install --cask cc-switch
Upgrade:
brew upgrade --cask cc-switch
Linux
Linux assets are available for both x86_64 and ARM64 (aarch64). Choose the file whose architecture tag matches your machine's uname -m output:
CC-Switch-v3.19.0-Linux-x86_64.AppImage/.deb/.rpmCC-Switch-v3.19.0-Linux-arm64.AppImage/.deb/.rpm
| Distribution | Recommended Format | Install Command |
|---|---|---|
| Ubuntu / Debian / Linux Mint / Pop!_OS | .deb |
sudo dpkg -i CC-Switch-*.deb or sudo apt install ./CC-Switch-*.deb |
| Fedora / RHEL / CentOS / Rocky Linux | .rpm |
sudo rpm -i CC-Switch-*.rpm or sudo dnf install ./CC-Switch-*.rpm |
| openSUSE | .rpm |
sudo zypper install ./CC-Switch-*.rpm |
| Arch Linux / Manjaro | .AppImage |
Make executable and run directly, or use AUR |
| Other distributions / unsure | .AppImage |
chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage |