Skip to content

Commit c3bb8e3

Browse files
authored
feat(billing): add cch attestation for OAuth requests (#8)
## Summary Add billing attribution header computation with cch request integrity hash for OAuth-authenticated API requests. This matches Claude Code's native client attestation protocol, enabling subscription-tier rate limits and fast mode access. The cch mechanism was reverse-engineered from Anthropic's custom Bun binary ([a10k.co writeup](https://a10k.co/b/reverse-engineering-claude-code-cch.html)) — it's xxHash64 with a fixed seed, masked to 5 hex chars. The fingerprint suffix is SHA-256 of salt + 3 characters from the first user message + version. - Add `billing.rs` module: `compute_fingerprint` (SHA-256 suffix), `build_billing_header` (header assembly with placeholder), `inject_cch` (xxHash64 body hash + single-occurrence replacement). - Integrate into `anthropic.rs`: for OAuth, prepend billing header as the first system block, serialize to string, compute and inject cch before sending. - Reorder `CreateMessageRequest` fields so `system` serializes before `messages`, ensuring the placeholder in the billing header is found first by `str::replacen` even when tool results contain the literal `cch=00000`. - Switch `stream_sse` from `serde_json::Value` + `.json()` to `String` + `.body()` to support post-serialization cch injection. - API key auth is unaffected — billing header is only injected for OAuth. ### Avoiding Claude Code's prompt cache bug Claude Code's Bun runtime performs a global in-place string mutation of `cch=` values across the entire request body, including historical tool results ([anthropics/claude-code#40652](anthropics/claude-code#40652)). This permanently invalidates prompt cache and wastes 30-50K+ tokens per turn. Our implementation is structurally immune: - `inject_cch` takes `&str` (immutable) and returns a new `String` — Rust's ownership model prevents in-place mutation. - `str::replacen(..., 1)` replaces only the first occurrence (the billing header's placeholder). - The conversation `messages` slice is never modified — it's serialized fresh each turn. ## Changes | File | Description | | ---- | ----------- | | `Cargo.toml` | Add `sha2 = "0.10"` and `xxhash-rust = { version = "0.8", features = ["xxh64"] }` to workspace dependencies | | `crates/oxide-code/Cargo.toml` | Wire up `sha2` and `xxhash-rust` | | `crates/oxide-code/src/client.rs` | Add `mod billing` | | `crates/oxide-code/src/client/billing.rs` | New module: `compute_fingerprint`, `build_billing_header`, `inject_cch` with 8 tests | | `crates/oxide-code/src/client/anthropic.rs` | Import `billing`, `ContentBlock`, `Role`; reorder `CreateMessageRequest` fields (`system` before `messages`); compute billing header for OAuth in `stream_message`; switch `stream_sse` body from `Value` to `String`; add `first_user_text` helper with 4 tests | | `CLAUDE.md` | Add `billing.rs` to crate structure diagram | | `docs/research/anthropic-api.md` | Replace "tamper-proof / unreplicable" characterization with full reverse-engineered algorithm, constants, known bugs, and oxide-code's approach | ## Test plan - [x] `cargo fmt --all --check` — clean - [x] `cargo build` compiles cleanly - [x] `cargo clippy --all-targets -- -D warnings` — zero warnings - [x] `cargo test` — 208 tests pass (12 new) - [x] `cargo llvm-cov --ignore-filename-regex 'main\.rs'` — 87% line coverage (`billing.rs` at 100%)
1 parent 57d1b7b commit c3bb8e3

9 files changed

Lines changed: 398 additions & 36 deletions

File tree

.cspell/words.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
anthropic
2+
anthropics
23
anyhow
34
claudemd
45
clippy
@@ -15,6 +16,7 @@ RAII
1516
ratatui
1617
replacen
1718
reqwest
19+
rfind
1820
rustls
1921
serde
2022
strum
@@ -23,3 +25,4 @@ thiserror
2325
tokio
2426
tracing
2527
venv
28+
xxhash

CLAUDE.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,43 @@ oxide-code is a terminal-based AI coding assistant written in Rust, inspired by
77
### CLI
88

99
```bash
10-
ox # Start an interactive session
10+
ox # Start an interactive session
1111
```
1212

1313
### Project Layout
1414

1515
```text
1616
.
17-
├── crates/oxide-code/ # Main binary crate
18-
├── docs/ # Roadmap and research notes
19-
└── target/ # Build output
17+
├── crates/oxide-code/ # Main binary crate
18+
├── docs/ # Roadmap and research notes
19+
└── target/ # Build output
2020
```
2121

2222
### Crate Structure (`crates/oxide-code/src/`)
2323

2424
```text
2525
.
26-
├── client.rs # Client module root
26+
├── client.rs # Client module root
2727
├── client/
28-
│ └── anthropic.rs # Anthropic Messages API streaming client
29-
├── config.rs # Configuration loading (env vars, model, base URL)
28+
│ ├── anthropic.rs # Anthropic Messages API streaming client
29+
│ └── billing.rs # Billing attribution header (fingerprint, cch attestation)
30+
├── config.rs # Configuration loading (env vars, model, base URL)
3031
├── config/
31-
│ └── oauth.rs # Claude Code OAuth credentials (macOS Keychain + file), token refresh, file locking
32-
├── main.rs # CLI entry point, agent loop, async REPL
33-
├── message.rs # Conversation message types
34-
├── prompt.rs # System prompt builder (section assembly, static content)
32+
│ └── oauth.rs # Claude Code OAuth credentials (macOS Keychain + file), token refresh, file locking
33+
├── main.rs # CLI entry point, agent loop, async REPL
34+
├── message.rs # Conversation message types
35+
├── prompt.rs # System prompt builder (section assembly, static content)
3536
├── prompt/
36-
│ ├── environment.rs # Runtime environment detection (platform, git, date)
37-
│ └── instructions.rs # Instruction file discovery and loading (CLAUDE.md, AGENTS.md)
38-
├── tool.rs # Tool trait, registry, definitions
37+
│ ├── environment.rs # Runtime environment detection (platform, git, date)
38+
│ └── instructions.rs # Instruction file discovery and loading (CLAUDE.md, AGENTS.md)
39+
├── tool.rs # Tool trait, registry, definitions
3940
└── tool/
40-
├── bash.rs # Shell command execution with timeout
41-
├── edit.rs # Exact string replacement in files
42-
├── glob.rs # File pattern matching (glob)
43-
├── grep.rs # Content search via regex
44-
├── read.rs # File reading with line numbers and pagination
45-
└── write.rs # File writing with directory creation
41+
├── bash.rs # Shell command execution with timeout
42+
├── edit.rs # Exact string replacement in files
43+
├── glob.rs # File pattern matching (glob)
44+
├── grep.rs # Content search via regex
45+
├── read.rs # File reading with line numbers and pagination
46+
└── write.rs # File writing with directory creation
4647
```
4748

4849
## Coding Conventions

Cargo.lock

Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ reqwest = { version = "0.12", default-features = false, features = [
3232
security-framework = "3"
3333
serde = { version = "1", features = ["derive"] }
3434
serde_json = "1"
35+
sha2 = "0.10"
3536
tempfile = "3"
3637
time = { version = "0.3", features = ["local-offset"] }
3738
tokio = { version = "1", features = [
@@ -50,6 +51,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [
5051
"env-filter",
5152
"fmt",
5253
] }
54+
xxhash-rust = { version = "0.8", features = ["xxh64"] }
5355

5456
[profile.release]
5557
lto = true

crates/oxide-code/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ regex.workspace = true
2323
reqwest.workspace = true
2424
serde.workspace = true
2525
serde_json.workspace = true
26+
sha2.workspace = true
2627
time.workspace = true
2728
tokio.workspace = true
2829
tracing.workspace = true
2930
tracing-subscriber.workspace = true
31+
xxhash-rust.workspace = true
3032

3133
[target.'cfg(target_os = "macos")'.dependencies]
3234
security-framework.workspace = true

crates/oxide-code/src/client.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod anthropic;
2+
mod billing;

0 commit comments

Comments
 (0)