Local reverse proxy that fixes Cline Desktop's "Token registration failed: 403" error on api.cline.bot.
Cline Desktop (WebView2 + Bun sidecar) sends TLS handshakes that Google Cloud Armor's JA3/JA4 fingerprinting flags as suspicious → HTTP 403 Forbidden.
The affected stacks: Go crypto/tls, Windows schannel, Bun usockets.
The one that passes: Python's OpenSSL (different ClientHello fingerprint).
Cline sidecar → HTTP → localhost:61022 (this proxy) → HTTPS → api.cline.bot
Python OpenSSL (passes JA3 check)
The proxy owns the entire auth lifecycle — it does NOT depend on the Cline desktop app for tokens:
--loginruns a WorkOS device-flow (prints a URL for you to confirm)- Registers with Cline API, stores tokens
- On every upstream request the proxy injects a fresh token
- If the token is near expiry it automatically refreshes via WorkOS + Cline register
The Cline app just needs baseUrl pointing at the proxy — it can use any dummy token.
| File | Purpose |
|---|---|
main.py |
CLI entry point (--port, --bind, --api-key, --log, --rate-limit, --desensitize, --login, --owned-auth) |
server.py |
HTTP server: Cline passthrough, /v1/models, /v1/chat/completions, /v1/messages |
upstream.py |
Python OpenSSL upstream client (Cloud Armor bypass, injects proxy token with workos: prefix) |
auth.py |
Full OAuth lifecycle: device flow, register, refresh, token store |
anthropic.py |
Anthropic Messages API ↔ OpenAI chat completions translation |
ratelimit.py |
Per-IP token-bucket rate limiter |
desensitize.py |
Content moderation trigger rewriting |
logging.py |
Request logging with credential redaction |
banner.py |
Startup banner |
config.py |
CLI config + env var overrides |
tests/selfcheck.py |
Unit tests |
# First time: login (opens a browser page for you to confirm)
python main.py --login
# Start proxy (auto-enables owned auth if tokens exist)
python main.py --port 61022
# Or manually force owned-auth
python main.py --port 61022 --owned-authThen in the Cline app settings or ~/.cline/data/settings/providers.json:
{ "providers": { "cline": { "settings": { "baseUrl": "http://127.0.0.1:61022" } } } }Cline bills by model ID: deepseek/deepseek-v4.1-flash is metered, cline-free/deepseek-v4.1-flash is not.
The proxy rewrites paid IDs to their free twins automatically (cached list from
/api/v1/ai/cline/recommended-models) and exposes the free IDs in /v1/models
so OpenAI-compatible clients can discover them.
Default 61022. Override via --port or CLINE_PROXY_PORT env var.
Cline uses WorkOS AuthKit device flow (client_01K3A541FN8TA3EPPHTD2325AR):
python main.py --loginrequests device code fromapi.workos.com- User confirms at
authkit.cline.bot/device?user_code=XXXX-XXXX - WorkOS tokens exchanged → registered with Cline API (
/api/v1/auth/register) - Tokens stored in
~/.cline/data/settings/cline_proxy_auth.json(+ mirrored toproviders.json) - Auto-refresh before expiry on every upstream request
python tests/selfcheck.py # 19 checksGo's crypto/tls produces a ClientHello that Cloud Armor blocks.
refraction-networking/utls bypasses JA3 but forces HTTP/2 (ALPN=h2 from the Chrome profile), and Go's HTTP/1.1 transport can't read h2 frames. Python is the correct TLS stack for this upstream.