Read this before installing. This project modifies an application you did not write, on your own machine, and it is honest about what that costs.
- Rewrites
resources\app.asarinside%LOCALAPPDATA%\AnthropicClaude\app-<version>\, appending two loader scripts to the app's main-process entry and to the preload used by the claude.ai view. - Where a build still carries Desktop's blanket
SendMessagebans, rewrites those two sites in the app's own main-process chunks so agents can message each other. Each edit asserts it matched exactly once, and a build with no match is accepted only if its asar header hash was audited by hand. - Overwrites the 64-character asar header hash embedded in
claude.exe, because Electron validates it and the app will not boot otherwise. - Keeps pristine copies as
app.asar.cuimod-backupandclaude.exe.cuimod-backup.node patch.js revertrestores both. Reinstalling or updating Claude Desktop also produces a clean install. install.ps1registers a logon scheduled task (ClaudeUiMod-Watcher) that re-applies the patch after app updates.
Modifying the signed binary invalidates its signature for that file. If your environment enforces application integrity, do not install this.
The main-process host runs with the full privileges of the Claude Desktop process. It deliberately exposes a developer surface over its private IPC channel:
fs.read/fs.list— read any file the user can readdev.eval— evaluate arbitrary code in the main processdev.webContents— enumerate views and their URLs- plugin
main.jshandlers can callevent.sender.executeJavaScriptand so run code in claude.ai's own page world
That is a local developer tool for a local user, and it is the reason the mod
never contextBridges anything: claude.ai's page JavaScript cannot reach the
RPC channel, so a compromised or hostile web page cannot call dev.eval. The
surface is reachable only from the mod's own isolated world — that is, from code
in this repository, plus anything you put in src/plugins/.
The practical consequence: a plugin you install is code running as the desktop
app. Treat third-party plugins exactly like any other program you run, and
read them before dropping them into src/plugins/.
Most plugins only draw UI. These do more:
-
frontend-pinattaches CDP Fetch to Code views. Dedicated worker asset requests arrive through their owning view's interceptor; workers are never attached or paused. It rewrites the Code document's entry and shared stylesheet URLs, serves the archived frontend's assets from local files, and holds the manifest's two bootstrap UI flags off. Cookies, account data, sessions, backend APIs and server-selected gated imports remain live. The archive contains original public CDN assets, not a signed-in document or account bootstrap. Missing or corrupt archived bytes abort locally and display a compatibility warning. The dashboard toggle detaches interception without reloading the open page. See the frontend pin boundary. -
file-pane-accesswraps the app's file-read handler. When the shell answers "not found" for a path the page asks for, it returns the file itself if it exists on your disk: any file you can read, up to 10 MB, read-only, with UNC and device paths refused. It also scans the session transcript to resolve bare file-name links. -
unlock-codelets a free-tier account use the Claude Code tab, and it does so on the client. It has three parts:- Account record. The main process injects
page.jsinto every claude.ai page and re-injects it every three seconds. The script finds the page's React Query client and rewrites the cachedcurrent_accountrecord in memory: for every organization membership it adds thechat,claude_pro,claude_max,raven,claude_codeandclaude_code_desktopcapabilities and setsrate_limit_tiertodefault_claude_max_20x, and in the model selector config it clears the disabled flag, theupgrade_requiredreason and the upgrade call to action from model rows. It sends nothing to Anthropic and changes nothing on the server. Requests to Anthropic still carry your real credential, and the server decides what the account may use, so Claude models stay bound by the real plan and limits. What changes is what the page believes about the account: the Code tab, model rows and any limit copy derived from the account record reflect the rewritten values until the page next refetches or reloads it. - Page cosmetics. The renderer half can hide the discovery takeover and the upsell and limit banners with a stylesheet. Both switches are in the dashboard.
- Routed credential. On sessions that run on routed (non-Claude) models it
substitutes the fixed string
cuimod-local-routerfor Anthropic's OAuth token, and only for the Code OAuth scope. Those requests go to the local router, never to Anthropic. Sessions on Claude models keep the app's own credential.
The dashboard's Enabled switch (
"unlock-code": { "enabled": false }in.state/settings.json) stops all three: the routed credential, the stylesheet, and the page script, which is retired and no longer injected. Reload the window afterwards so the page refetches its account record. An emptysrc/plugins/unlock-code/DISABLED.jsonremoves the plugin entirely. - Account record. The main process injects
-
sendmessage-unban,model-probe-skipandfast-modewrap methods on running Claude Code session objects in the main process (SendMessagedeny hooks,setModel,setFastMode). Their state files hold per-session switches and model ids, not conversation content. -
campaignsregisters an in-process tool server andPreToolUsehooks on the sessions you bind to a campaign. Those hooks can deny a tool call. Its state lives in.state/campaigns/, including the briefs, evidence paths and check output you give it. -
computer-usewrites your Desktopclaude_desktop_config.jsonand the app's preference store to toggle the native Windows Computer Use backend. It writes nothing until you flip its dashboard switch, so a fresh install leaves Desktop's own setting exactly as it was. -
campaignsevidence links open only inert file types (text, logs, JSON, images, PDF, media). Anything else a worker wrote is revealed in Explorer instead of being opened, so a prompt-injected worker cannot get a script run by your click. -
queuepersists queued messages, including pasted images, in plaintext in.state/queue.jsonwhile they are queued.
Most UI plugins make no network requests. The voice plugin talks only to its
token-protected workstation companion on loopback. The router in router/ is
different, and calling it out plainly matters more than the rest of this document.
When the routing plugin is enabled, Claude Desktop spawns each Claude Code CLI
with ANTHROPIC_BASE_URL pointing at 127.0.0.1:8789. Every request your
sessions make then passes through a process on your machine that this repository
controls. It listens on loopback only and is never exposed off the host, but
within the host it sees your prompts, your files-as-context, and your responses.
What it does with them:
- Anthropic traffic is forwarded byte-identical to
api.anthropic.comunless you have activated a prompt policy, in which case it rewrites the JSON body exactly as the dashboard shows. gpt-*traffic is translated to the OpenAI Responses API and sent tochatgpt.comon the OAuth token the Codex CLI already holds, read fromCODEX_AUTH_FILE. No metered API key, no key of ours. The router also reads your plan's usage endpoint onchatgpt.comfor the quota meters, and refreshes the token atauth.openai.comwhen it is about to expire.- Meta traffic (Muse Spark) goes to Meta's Anthropic-Messages endpoint with
the key you put in
.state/provider-secrets.json. The catalogue's "Contributor" row is Meta's contributor tier; its catalogue note says Meta may train on prompts and completions. The plain row carries no such note. - Command Code traffic (DeepSeek, GLM, Kimi, Qwen and MiniMax model ids) is
translated to OpenAI Chat Completions and sent to
api.commandcode.aiwith the key from.state/provider-secrets.json(fieldcommand-code) or theCOMMAND_CODE_API_KEYenvironment variable. Every request carriesx-cmd-zdr: 1, which asks for zero data retention; whether Command Code honours it is theirs to say, and their terms govern the prompts. The quota meter calls that service's read-only billing endpoints (/alpha/whoami,/alpha/billing/credits,/alpha/billing/subscriptions) with the same key and keeps only sanitized quota fields. - Loopback endpoints are unauthenticated.
/healthz,/usage/api,/limitsz,/throughputz,/contextzand the Usage Atlas answer any local process. They carry no credential values and no message content.POST /resetzis the exception worth knowing: it asks the Codex CLI to redeem a plan reset credit, so any local process could spend one. The router never does it on its own, and withoutforce=1it refuses unless a usage window is actually exhausted. - Token counts, model names and timings are written to disk under
~/.claude/gemini-bridge/(a legacy directory name;MODEL_ROUTER_STATE_DIRoverrides it) so the usage dashboard has a history. Response text is never written there. - Your system prompt and tool descriptions are. Every request that carries
tools — which is every agent turn — writes its system prompt and tool
descriptions to
prompt-policy-capture-v2.jsonin that same directory, before and after any active policy. This is not opt-in and there is no flag that disables it. It exists so a replacement policy cannot destroy the only editable copy of what your client actually sent. The file keeps the most recent 24 entries, one per provider and session. Control-plane requests are the only thing excluded. If your system prompt contains anything you would not want sitting in a plaintext file on your disk, delete that file, or pointMODEL_ROUTER_STATE_DIRsomewhere you control. - User and assistant message bodies are not captured by that file — only the system prompt and the tool schemas.
GEMINI_BRIDGE_DUMP=1additionally writes complete outgoing request bodies toreqdump/in the state directory. That one is off by default. Turn it on only when debugging, and remember what is in the files afterwards.
Nothing is uploaded anywhere you did not already send it, and there is no telemetry, analytics, or update check of any kind.
Installing voice-agent/ adds a second trusted program with access to the
interactive user's microphone, speakers, Claude transcript files and SSH client.
The workstation API and compute API bind to loopback and require independent
bearer tokens. The compute listener is reached through an SSH local-forward; it
is not exposed as a new LAN service.
While the recorder runs, it records everything the microphone hears. It is a
separate process that writes every captured block, silence included, to
.state/voice-agent/raw/ before any other component sees it. The companion
starts it on demand and it exits after 15 minutes without a consumer, so
capture is not tied to a wake phrase or to the microphone button. Finished
chunks are then moved to the compute host and stored there as bit-verified FLAC
under ~/.local/share/cuimod-voice/archive/. The workstation copy is deleted
only after the archive confirms the same checksum and sample count. Nothing in
this repository prunes that archive, and the retention setting does not apply
to it; delete it yourself. Transcribed utterances, with their transcript
sidecars (raw ASR text and acoustic emotion scores), and rotated companion event
history move to the same archive.
On the workstation, retention deletes completed utterance WAV files older than
the configured number of days; zero disables automatic deletion. The optional
continuous-journal mode additionally writes rolling WAV files of the whole
stream under .state/voice-agent/audio/. Treat the raw spool, the archive and
the transcript sidecars as sensitive audio and transcript data.
The correction and summary endpoint configured in compute.json receives
transcript text. The shipped configuration points to a loopback model service.
Changing it to a remote URL changes the privacy boundary. Ordinary voice prompts
submitted into Claude still go to the provider selected for that Claude chat.
Wake phrases and voice characteristics reduce accidental activation; they are not
authentication. Claude's existing tool permissions remain the authority after a
voice prompt is submitted.
- The router reads the Codex OAuth token to make OpenAI requests on your behalf.
It is read from disk per request and never copied into this repository. It is
not read-only: when the access token is near expiry the router refreshes it
and writes the rotated tokens back to
auth.jsonatomically, because the old refresh token is spent and the Codex CLI would otherwise be signed out. - Added ChatGPT accounts are stored in plaintext. The account picker keeps a
full copy of each account's
auth.jsonunder.state/openai-accounts/(ignored by git), and switching, removing or auto-switching accounts replaces the canonical~/.codex/auth.jsonwith one of those copies. Treat that directory exactly likeauth.jsonitself. The sign-in runscodex loginagainst a temporary home and copies the result only if it succeeds. - Compatible-provider keys, and the Command Code key, live in
.state/provider-secrets.json, which is gitignored. Nothing else is a valid place for them. The Command Code adapter also accepts the key from theCOMMAND_CODE_API_KEYenvironment variable. - No credential value appears in
/healthz,/limitsz,/usage/api, the router log, or any renderer RPC. Those surfaces report whether a credential reads, not what it is, and the test suite asserts it.
router/adapters/ is an extension point: a directory dropped in there is loaded
into the router and can see every request routed to the models it claims, plus
whatever credentials it reads. That is the same trust level as a plugin, for the
same reason. Read one before you install it. The one that ships,
router/adapters/command-code/, reads the Command Code key and relays prompts
to that service as described above.
| path | why |
|---|---|
~/.claude/tasks/<cliSessionId>/*.json |
the task list the rail renders |
~/.claude/projects/<slug>/<cliSessionId>.jsonl |
your full CLI conversation transcripts. goal scans them for goal records; subagents and throughput read them to attribute work to a named agent. Read only, incrementally, never copied elsewhere. |
%APPDATA%\Claude\claude-code-sessions\ |
maps the URL's desktop session id to the task-store key |
%APPDATA%\Claude\plan-usage-history.json |
rate-limit percentages for the usage meters |
~/.claude/gemini-bridge/ |
router log, token ledger, usage accounting, and the prompt capture described above |
Codex auth.json |
the OAuth token for the OpenAI arm. Read per request; rewritten on token refresh and on account switch |
.state/openai-accounts/ |
one stored copy of each added ChatGPT account's auth.json |
%APPDATA%\Claude\claude_desktop_config.json |
rewritten by computer-use to set the native Computer Use preferences |
.state/queue.json, .state/campaigns/, .state/fast-mode.json |
queued messages and images, campaign records, per-session fast switches |
~/.codex/models_cache.json |
the Codex client's model list, read to know which models offer the priority tier |
.state/ in this repo |
plugin settings, captured plan limits, provider keys if you add any, and optional voice tokens/audio/transcript evidence |
plan-usage-capture additionally wraps Electron's net.fetch in the main
process to read Desktop's own organization usage response as it arrives, and
persists the rate-limit numbers from it to .state/claude-plan-resets.json.
That is an interception of the app's own network call, on your machine, and it
is how the Claude quota meter gets its data.
All of it stays on disk, on your machine.
- The core mod has no dependencies beyond a pinned pure-JS tokenizer
(
js-tiktoken). The optional voice services have separately pinned Python requirements listed undervoice-agent/. - No contact with the Claude Code CLI child processes or their stdio pipes. UI reloads never replace a running agent; routing only decorates the environment at spawn.
- No telemetry or update check. Outbound connections are provider requests (Anthropic, OpenAI's inference, usage and token endpoints, Command Code's inference and billing endpoints when you add its key, and any catalogue backend you added a key for) plus, when voice is installed, the configured SSH connection to its compute host.
Open an issue for anything that is not itself exploitable.
For anything that is, tick Confidential on the new-issue form before you submit it — the checkbox sits just under the description box. A confidential issue is readable only by you and the project members, so a working exploit against a tool that patches a signed binary does not go public before there is a fix for it.
Report to Anthropic instead, not here, if you find a flaw in Claude Desktop itself. This project is unofficial and unaffiliated; nothing in it should be taken as a statement about the security of the app it patches.