Credential Proxy
Extensions run in two contexts: extension host (Node, trusted) and webview (browser sandbox, less trusted). Without a proxy, API keys would need to reach the webview for direct LLM calls. The credential proxy keeps keys in the extension host and only exposes a localhost URL + session token to the client.
Providers
Section titled “Providers”| Provider | Upstream | Auth header | Format |
|---|---|---|---|
| Anthropic | https://api.anthropic.com | x-api-key | Raw key |
| OpenAI | https://api.openai.com | Authorization | Bearer {key} |
| Groq | https://api.groq.com/openai | Authorization | Bearer {key} |
| DeepSeek | https://api.deepseek.com | Authorization | Bearer {key} |
| Qwen | https://dashscope-intl.aliyuncs.com/compatible-mode | Authorization | Bearer {key} |
| GLM | https://open.bigmodel.cn/api/paas | Authorization | Bearer {key} |
| Grok | https://api.x.ai | Authorization | Bearer {key} |
| Tavily | https://api.tavily.com | Authorization | Bearer {key} |
| Local | http://localhost:11434 | Authorization | Bearer {key} |
Anthropic also gets anthropic-version: 2023-06-01 on every request.
Session token
Section titled “Session token”Generated on every proxy start via crypto.randomBytes(32). Header name: x-codebuddy-proxy-token. Missing or wrong token → 403 Forbidden. Prevents other processes on the machine from using the proxy.
Rate limiting
Section titled “Rate limiting”Per-provider token bucket:
- Each provider has its own bucket with
maxTokens+refillRate. - Local providers exempt (no upstream rate caps).
- Empty bucket →
429 Too Many RequestswithRetry-After. - Live-reloads on config change (buckets reset).
Security hardening
Section titled “Security hardening”Localhost-only binding. srv.listen(0, "127.0.0.1", cb) — never exposed to the network.
Header stripping — sensitive headers removed from the client request before forwarding:
authorizationx-api-keyx-goog-api-keyhostconnection,keep-alive,transfer-encoding
Then the proxy injects the correct credentials from the keychain.
Body limits:
- Max body: 10 MB →
413 Payload Too Large - Client idle: 30 s between body chunks → drop
- Upstream: 5 min timeout (streaming responses)
Error mapping — no internal details leaked:
| Upstream | Proxy | Message |
|---|---|---|
ECONNREFUSED | 502 | Upstream refused connection |
ECONNRESET | 502 | Upstream reset connection |
ETIMEDOUT | 504 | Upstream connection timed out |
ENOTFOUND | 502 | Upstream host not found |
ECONNABORTED | 504 | Request timed out |
Audit log
Section titled “Audit log”Ring buffer, 1000-entry cap, O(1) writes:
{ "timestamp": 1711612800000, "provider": "anthropic", "method": "POST", "path": "/v1/messages", "statusCode": 200, "latencyMs": 1523}Oldest entries evicted when full — bounded memory.
Lifecycle
Section titled “Lifecycle”start()is promise-coalesced — concurrent calls share one promise.dispose()drains: stop accepting → force-close tracked sockets after grace period → config watcher disposed →stopped.
Settings
Section titled “Settings”| Setting | Default | Purpose |
|---|---|---|
codebuddy.credentialProxy.enabled | false | Enable the proxy |
codebuddy.credentialProxy.rateLimits | {"anthropic":60,"openai":60,"groq":30,"deepseek":60,"qwen":60,"glm":60,"grok":60} | Per-provider requests/min (override individual providers) |
How it integrates
Section titled “How it integrates”With the proxy enabled, getAPIKeyAndModel() returns a proxy URL instead of the real upstream. LangChain wrappers (and completion providers) hit http://127.0.0.1:{port}/{provider}/... — the proxy injects credentials and forwards. Client-side code never sees the real key.