Skip to content

feat(billing): add cch attestation for OAuth requests - #8

Merged
hakula139 merged 6 commits into
mainfrom
feat/billing-cch
Apr 5, 2026
Merged

feat(billing): add cch attestation for OAuth requests#8
hakula139 merged 6 commits into
mainfrom
feat/billing-cch

Conversation

@hakula139

Copy link
Copy Markdown
Owner

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) — 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). 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

FileDescription
Cargo.tomlAdd sha2 = "0.10" and xxhash-rust = { version = "0.8", features = ["xxh64"] } to workspace dependencies
crates/oxide-code/Cargo.tomlWire up sha2 and xxhash-rust
crates/oxide-code/src/client.rsAdd mod billing
crates/oxide-code/src/client/billing.rsNew module: compute_fingerprint, build_billing_header, inject_cch with 8 tests
crates/oxide-code/src/client/anthropic.rsImport 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.mdAdd billing.rs to crate structure diagram
docs/research/anthropic-api.mdReplace "tamper-proof / unreplicable" characterization with full reverse-engineered algorithm, constants, known bugs, and oxide-code's approach

Test plan

  • cargo fmt --all --check — clean
  • cargo build compiles cleanly
  • cargo clippy --all-targets -- -D warnings — zero warnings
  • cargo test — 208 tests pass (12 new)
  • cargo llvm-cov --ignore-filename-regex 'main\.rs' — 87% line coverage (billing.rs at 100%)

Compute and inject the billing attribution header (fingerprint + cch
body 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 fingerprint is SHA-256(salt + 3 chars from first user message +
version), truncated to 3 hex chars. The cch is xxHash64(serialized
body with placeholder, seed) masked to 5 hex chars, replacing the
cch=00000 placeholder via str::replacen. Only the first occurrence
is replaced — struct field ordering ensures system serializes before
messages to avoid collision with tool results containing the literal
placeholder.
Unlike Claude Code's Bun runtime (which mutates strings in-place and
can poison prompt cache via global cch= substitution in historical
tool results), our implementation creates a fresh String per request
and never modifies the conversation history.
Replace the earlier "tamper-proof / unreplicable" characterization
with the actual algorithm, constants, and known bugs based on the
a10k.co reverse engineering writeup.
@hakula139hakula139 added the enhancement New feature or request label Apr 5, 2026
@hakula139hakula139 self-assigned this Apr 5, 2026
- Fix workspace Cargo.toml: move xxhash-rust after tracing-subscriber
to restore alphabetical dependency order.
- DRY: use CCH_PLACEHOLDER constant in build_billing_header instead of
hardcoding the literal.
- Reorder tests to match convention: happy path → variants → edge cases.
@hakula139
hakula139 merged commit c3bb8e3 into mainApr 5, 2026
1 check passed
@hakula139
hakula139 deleted the feat/billing-cch branch April 5, 2026 17:32
hakula139 added a commit that referenced this pull request Jun 26, 2026
## Summary
Refresh dependencies across both ecosystems and clear the two open
Dependabot DoS advisories (#7, #8).
- **Rust:** `cargo update` (77 crates, semver-compatible) plus two
direct major bumps — `nix` 0.30 → 0.31 and `toml` 0.8 → 1. Both have
narrow usage (`nix` only `killpg`/`Pid`/`Signal` in `bash.rs`; `toml`
only `from_str` deserialization), so the migrations are no-ops at the
call sites.
- **Node:** `pnpm update --latest` (cspell 10.0.1, markdownlint-cli2
0.22.1) plus pnpm `overrides` forcing the patched `js-yaml` and
`markdown-it`.
## Design decisions
- **DoS fix needs an override, not a direct bump.** Both `js-yaml` and
`markdown-it` reach the tree only transitively through
`markdownlint-cli2@0.22.1`, which still pins the vulnerable versions, so
`overrides` is the only way to force the patched releases.
- **`js-yaml` capped at `^4.2.0`, not `>=4.2.0`.** The latest is 5.1.0,
but 5.x drops the `default` ESM export `markdownlint-cli2` imports,
which breaks `pnpm lint`. The advisory is patched in 4.2.0, so the 4.x
line fixes the alert without breakage.
- **`ratatui` / `crossterm` / `syntect` left on their current majors.**
They are already at their newest major; only `nix` and `toml` had a
clean newer major among our direct deps.
## Changes
| File | Description |
| ---------------- |
------------------------------------------------------------------------------
|
| `Cargo.toml` | Bump `nix` 0.30 → 0.31, `toml` 0.8 → 1 |
| `Cargo.lock` | Relock; `cargo update` across 77 crates |
| `package.json` | cspell `^10.0.1`, markdownlint-cli2 `^0.22.1`, pnpm
overrides for the DoS deps |
| `pnpm-lock.yaml` | Relock onto patched + latest versions |
## Test plan
- [x] `cargo fmt --all --check` — clean
- [x] `cargo clippy --all-targets -- -D warnings` — zero warnings
- [x] `cargo test` — 2094 pass
- [x] `pnpm lint` — 48 files, 0 errors
- [x] `pnpm spellcheck` — 184 files, 0 issues
- [x] No `js-yaml@4.1.1` / `markdown-it@14.1.1` left in the lock
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@hakula139