clauth keeps live Claude Code OAuth tokens on disk and replaces its own binary over the network. Both are worth scrutiny, so this doc spells out what it stores, what it talks to, what can touch your account, how releases get verified, and how to switch each thing off.
Report privately through GitHub: open the repo's Security tab and pick Report a vulnerability. Please don't file a public issue for anything exploitable. A description, the affected version, and repro steps are enough to get started.
Only the latest release. Binary installs stay current through the verified
auto-updater below; cargo installs update with cargo install clauth.
Per-profile state lives under ~/.clauth/. Credentials are stored as tightly as the
OS allows.
| Path | Contents | Unix mode |
|---|---|---|
~/.clauth/profiles/<name>/credentials.json |
OAuth token snapshot | file 0600, dirs 0700 |
~/.clauth/profiles/<name>/config.toml |
base URL, API key (endpoint profiles), env block | 0600 |
~/.clauth/profiles/<name>/usage_cache.json |
last-known utilization and plan | 0600 |
provider stats cache (~/.clauth/) |
third-party account state | file 0600, dir 0700 |
- Writes are atomic. The temp file gets mode
0600at creation, not a chmod afterward, so a loose umask never leaves a readable window; it's fsynced, then renamed into place. A rotation caught mid-write lands ascredentials.json.pendingand is promoted only once it's durable. - Modes are enforced on Unix. On Windows, access falls to the default user-profile ACLs, which clauth does not loosen.
- A switch rewrites two things:
~/.claude/.credentials.jsonand theenvblock of~/.claude/settings.json. The rest of~/.claude/is left alone.
Every request clauth makes, and what rides along with it:
| Endpoint | When | Carries |
|---|---|---|
api.github.com/repos/uwuclxdy/clauth/releases/latest + release assets |
background update check on launch (binary installs only) | no credentials, just a User-Agent |
api.anthropic.com/v1/oauth/token |
lazy token refresh (on a 401) and t force-rotate |
your stored refresh token |
api.anthropic.com/api/oauth/usage |
usage poll on the refresh interval | access token (Bearer) |
api.anthropic.com/api/oauth/profile |
plan-tier detection | access token |
api.anthropic.com/v1/messages |
auto-start kick (opt-in, off by default) | access token; a 1-token Haiku request |
status.claude.com/api/v2/incidents.json |
Status tab and background poll | no credentials |
raw.githubusercontent.com/BerriAI/litellm/... |
model price table for the Tokens tab cost lens, fetched and disk-cached | no credentials |
api.deepseek.com/user/balance |
only for profiles whose base URL is DeepSeek | that provider's API key |
| a custom base URL you set | requests against an API-endpoint profile | whatever you configured |
OAuth tokens go to api.anthropic.com and nowhere else. clauth runs no telemetry or
analytics; it talks to the hosts above and no others.
Two code paths can change account state. Both are narrow and both are documented.
- Auto-start kick. A real, billed
/v1/messagescall (max_tokens = 1, a fraction of a cent) under your own OAuth token, with the Claude Code client identity. It's the same request Claude Code makes on startup, and it exists to arm the 5-hour usage window. Off by default, OAuth profiles only; enable it per profile on the Setup tab or withauto_start = true. - Token refresh. Anthropic refresh tokens are single-use, so refreshing spends
the stored token for a fresh pair. It's lazy: it fires only when a usage query
returns 401, never ahead of time, unless you press
tto force it.
Nothing else sends inference or writes to your account.
Binary installs check for a newer release in the background on launch. Every step fails closed, so if any of them errors the update is skipped and the running binary stays put:
- Ask the GitHub releases API for the latest tag; stop if it isn't newer.
- Download
sha256sums.txt. A fetch error stops here (no integrity, no update). - Download
sha256sums.txt.minisigand check it against a minisign public key pinned at compile time. A missing or bad signature stops the update. The key is a constant, so nothing at runtime can swap it out. - Download the platform asset (10 MB ceiling) and check its SHA-256 against the now-trusted sums file. A mismatch stops the update.
- Write to a temp file, fsync, then self-replace atomically. The new binary takes over on the next launch.
cargo installs (binary under ~/.cargo/bin) are told an update exists but never
replaced. CLAUTH_NO_UPDATE=1 turns the whole thing off.
Releases are signed in CI with a passwordless minisign key kept as a GitHub Actions
secret; the signing step writes the key to disk and deletes it on exit. The public
half is pinned in src/update.rs.
install.sh (the curl | bash path) uses cargo when it's available. When it pulls
a prebuilt binary instead, it downloads sha256sums.txt from the same release and
checks the binary against it before installing, failing closed on a download or
checksum error. It writes nothing to your shell profile and only prints a PATH hint
when the install dir isn't already on it. If piping a script to a shell isn't your
thing, cargo install clauth does the same job.
clauth start <profile> [claude args...]runsclaude(found onPATH) withCLAUDE_CONFIG_DIRpointed at the profile's isolated runtime, forwarding your extra args. Args go through an argument vector, never a shell, so there's no shell-injection path.- On Linux, opening a status-incident link runs
xdg-open <url>with null stdio. The URL comes from the Statuspage feed and is passed as a single argument. - clauth runs no other external commands.
On the first TUI launch clauth offers to install shell completions. For bash and zsh
it asks before adding a source line to your rc file ([Y/n], interactive sessions
only); fish gets its own completions dir. The answer is saved to
~/.clauth/.completions_installed so the prompt doesn't come back.
CLAUTH_NO_COMPLETIONS=1 skips it.
unsafeis denied across the crate (unsafe_code = "deny",unsafe_op_in_unsafe_fn = "deny").- CI runs
cargo fmt --check,cargo clippy --all-targets -D warnings, and the test suite on every push and PR. cargo-deny(advisories denied by default, license allowlist, sources locked to crates.io,opensslbanned in favor of rustls) andcargo-auditboth run in CI.Cargo.lockis committed and dependency versions are pinned.
| Switch | Effect |
|---|---|
CLAUTH_NO_UPDATE=1 |
disables all background update checks and self-replacement |
CLAUTH_NO_COMPLETIONS=1 |
skips the first-run completion-install prompt |
install.sh --nocargo |
forces a verified binary download instead of cargo install |
cargo install |
never self-replaces; update with cargo install clauth |