# MITM Traffic Interception — Research & Status ## Goal Capture the LS's LLM API traffic (requests + responses, including system prompts and token usage) by routing it through our MITM proxy. ## Key Discovery: How the LS Makes LLM API Calls The LS does **NOT** use gRPC for LLM API calls. It uses: - **Protocol**: Standard HTTPS POST with Server-Sent Events (SSE) - **Endpoint**: `https://daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse` - **HTTP client**: `ApiServerClientV2` — a Go HTTP client that creates its own `tls.Config` and transport, **ignoring `HTTPS_PROXY` by default** The Go HTTP client for LLM API calls is separate from the one used for Unleash (feature flags) and other auxiliary traffic. The Unleash client respects proxy settings, but the LLM client does not. ## What We Tried ### 1. Extension Patch — `detectAndUseProxy` ✅ Partial **Status**: Applied and still active. Harmless. The extension sends a protobuf field `detect_and_use_proxy` (field 34) to the LS during initialization. By default, it's set to `UNSPECIFIED` (0), meaning the LS ignores proxy env vars. **Patch applied:** ```bash sudo sed -i -E 's/detectAndUseProxy=[^,;)]+/detectAndUseProxy=1/g' \ /usr/share/antigravity/resources/app/extensions/antigravity/dist/extension.js ``` **Enum values:** - 0 = `DETECT_AND_USE_PROXY_UNSPECIFIED` (default, ignore proxy) - 1 = `DETECT_AND_USE_PROXY_ENABLED` - 2 = `DETECT_AND_USE_PROXY_DISABLED` **Result:** Unleash/aux traffic now routes through `HTTPS_PROXY`. But the LLM API client (`ApiServerClientV2`) has its own transport that ignores this flag. LLM calls still go direct to Google. **Verify:** `grep -o 'detectAndUseProxy=[^;]*' /usr/share/antigravity/resources/app/extensions/antigravity/dist/extension.js` → should show `detectAndUseProxy=1` **Re-apply after updates:** Yes, must re-apply after every Antigravity update. ### 2. MITM Wrapper (`mitm-wrapper.sh`) ✅ Works for Env Vars Sets `HTTPS_PROXY` and `SSL_CERT_FILE` on the LS process by wrapping the binary. **How it works:** 1. Renames real binary to `.real` 2. Places a shell script wrapper at the original path 3. Wrapper sets env vars and execs the real binary with all original args **Result:** The wrapper correctly sets env vars on the LS process (verified via `/proc//environ`). Combined with the extension patch, Unleash traffic routes through the proxy. But LLM API calls still bypass — the `ApiServerClientV2` Go HTTP client doesn't honor `HTTPS_PROXY`. ### 3. iptables REDIRECT — ALL Port 443 ❌ Failed Redirected all outbound port 443 traffic from the user's UID to the MITM proxy. **Problems encountered:** 1. **Redirect loop** — proxy's own upstream connections got caught by iptables, creating infinite loops → fd exhaustion → crash 2. **Fixed loop with GID bypass** — running proxy with `sg mitm-bypass` and excluding GID in iptables. This fixed the loop. 3. **Broke Antigravity** — ALL HTTPS traffic (telegram, discord, microsoft telemetry, extension marketplace, etc.) went through the proxy. The TLS passthrough worked technically but was too disruptive. 4. **TLS trust failure** — even with the MITM wrapper setting `SSL_CERT_FILE`, the LS's Go LLM client likely uses a custom `tls.Config` with its own root CAs, not the system pool. So it rejected our MITM CA cert. **Abandoned.** Too disruptive, and the fundamental TLS trust issue remained. ### 4. DNS Redirect (`/etc/hosts`) ❌ Failed Redirected only `daily-cloudcode-pa.googleapis.com` to 127.0.0.1 via `/etc/hosts`, then used a targeted iptables rule for `127.0.0.1:443` only. **Problems:** - Same TLS trust issue — the Go LLM client rejected our MITM CA - Needed `dig @8.8.8.8` bypass for upstream resolution (implemented but untested) **Abandoned.** TLS trust is the blocker. ## The Core Blocker **The LS's Go LLM HTTP client (`ApiServerClientV2`) uses a custom `tls.Config` that does NOT read from `SSL_CERT_FILE` or the system CA store.** It likely has its own hardcoded/embedded root CAs. This means: - Even if we redirect traffic to our MITM proxy ✅ - Even if the MITM generates valid certs for the domain ✅ - The LS rejects the cert because it doesn't trust our CA ❌ ## Potential Solutions (Untried) ### A. Binary Patching Patch the Go binary to accept our CA or disable cert verification. - Find the `tls.Config` setup in the binary - Modify `InsecureSkipVerify` to `true`, or inject our CA cert DER bytes - Very fragile, breaks on updates ### B. LD_PRELOAD Hook Hook `connect()` syscall to redirect traffic. - **Won't work** for Go — Go uses raw syscalls, not libc wrappers ### C. Network Namespace Run the LS in an isolated network namespace with custom routing. - Complex setup, but clean isolation - The standalone LS work would feed into this ### D. Standalone LS with Full Control Get standalone LS cascades working (see `docs/standalone-ls-todo.md`), then have full control over the process environment, including: - Custom CA trust - Custom DNS resolution - Custom proxy settings - Network namespace isolation **This is probably the best long-term approach.** ### E. Kernel-level TLS Interception (eBPF) Use eBPF to intercept TLS records pre-encryption. - Very powerful, can read plaintext before encryption - Complex, requires kernel support (>= 4.18) - Tools: `bpftrace`, custom eBPF programs, `ecapture` ### F. `SSLKEYLOGFILE` + Passive Capture - Go doesn't support `SSLKEYLOGFILE` (confirmed by testing) - Could patch the binary to enable it, but same fragility as option A ### G. ptrace-based Interception Use `ptrace` to intercept `write()`/`sendmsg()` syscalls on TLS sockets. - Can read plaintext data being written to TLS connections - Tools: `strace -e trace=write -p ` (but output is messy) - Better: custom ptrace tool that filters for TLS socket FDs ## Technical Details ### Model IDs | Placeholder | Model | | ------------------------- | ------------------- | | `MODEL_PLACEHOLDER_M18` | Gemini 3 Flash | | `MODEL_PLACEHOLDER_M8` | Gemini 3 Pro (High) | | `MODEL_PLACEHOLDER_M7` | Gemini 3 Pro (Low) | | `MODEL_PLACEHOLDER_M26` | Claude Opus 4.6 | | `MODEL_PLACEHOLDER_M12` | Claude Opus 4.5 | | `MODEL_CLAUDE_4_5_SONNET` | Claude Sonnet 4.5 | ### LS Binary Location `/usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64` ### API Endpoint `https://daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse` ### Protobuf Field 34 — `detect_and_use_proxy` - Part of the init metadata sent from extension to LS via stdin - Enum: `DetectAndUseProxy` (0=UNSPECIFIED, 1=ENABLED, 2=DISABLED) - Controls whether auxiliary HTTP clients honor `HTTPS_PROXY` - Does NOT control the LLM API client ### Unleash Feature Flags - Authorization: `*:production.e44558998bfc35ea9584dc65858e4485fdaa5d7ef46903e0c67712d1` - Endpoint: `antigravity-unleash.goog` - App name: `codeium-language-server` ### Files Modified (Current State) - `extension.js` — `detectAndUseProxy=1` (harmless, keeps working) - Everything else — clean/reverted ## Code Changes Made (in the proxy) 1. **Transparent proxy mode** (`src/mitm/proxy.rs`) — supports iptables REDIRECT by detecting raw TLS ClientHello and extracting SNI 2. **CryptoProvider init** (`src/main.rs`) — prevents rustls panic under load 3. **PID detection fix** (`src/backend.rs`) — prefers `.real` binary PID over wrapper shell script PID 4. **SS fallback** (`src/backend.rs`) — discovers LS port via `ss` when log file doesn't have it 5. **DNS bypass** (`src/mitm/proxy.rs`) — `connect_upstream` resolves via `dig @8.8.8.8` to bypass `/etc/hosts` 6. **Scripts** — `dns-redirect.sh`, `iptables-redirect.sh` (both functional) ## Cleanup Checklist If things are broken, undo in this order: ```bash # 1. Remove iptables rules sudo ./scripts/iptables-redirect.sh uninstall sudo ./scripts/dns-redirect.sh uninstall # 2. Remove /etc/hosts entries (verify manually) sudo grep -v "antigravity-mitm" /etc/hosts | sudo tee /etc/hosts.tmp && sudo mv /etc/hosts.tmp /etc/hosts # 3. Uninstall wrapper sudo ./scripts/mitm-wrapper.sh uninstall # 4. Remove system CA sudo rm -f /usr/local/share/ca-certificates/antigravity-mitm.crt sudo update-ca-certificates # 5. Restart Antigravity ``` ## Next Steps → See `docs/standalone-ls-todo.md` for standalone LS isolation work → See `docs/ls-binary-analysis.md` for comprehensive binary reverse engineering ## New Findings (from binary analysis) ### Alternative to Polling: `StreamCascadeReactiveUpdates` The LS has a streaming gRPC method `StreamCascadeReactiveUpdates` that pushes cascade state changes in real-time via server-sent streaming. The extension uses this instead of polling `GetCascadeTrajectorySteps`. **Potential improvement:** If we switch from polling to this streaming RPC, we'd get lower latency and less backend traffic. However, our current polling approach works reliably and doesn't require maintaining a long-lived gRPC stream. ### Quota Endpoint: `retrieveUserQuota` The `PredictionService/RetrieveUserQuota` gRPC method and `v1internal:retrieveUserQuota` REST endpoint provide quota/credit information. This could be used to implement a proper `/v1/quota` endpoint instead of scraping the LS's own quota tracking. ### `internalAtomicAgenticChat` A REST endpoint that appears to handle the entire agentic chat loop atomically (tool calls + responses in one request?). Investigation needed to understand the request/response format. ### Credits System The `google/internal/cloud/code/v1internal/credits` proto package exists with `Credits_CreditType` enum. The `CASCADE_ENFORCE_QUOTA` config key controls whether quotas are enforced. Related methods: `AddExtraFlexCreditsInternal`, `GetTeamCreditEntries`, `GetPlanStatus`.