- Spawn standalone LS as dedicated 'antigravity-ls' user via sudo - UID-scoped iptables redirect (port 443 → MITM proxy) via mitm-redirect.sh - Combined CA bundle (system CAs + MITM CA) for Go TLS trust - Transparent TLS interception with chunked response detection - Google SSE parser for streamGenerateContent usage extraction - Timeouts on all MITM operations (TLS handshake, upstream, idle) - Forward response data immediately (no buffering) - Per-model token usage capture (input, output, thinking) - Update docs and known issues to reflect resolved TLS blocker
5.2 KiB
MITM Traffic Interception — Status
Status: ✅ FULLY WORKING (Standalone Mode)
MITM interception is operational for the standalone LS. The proxy intercepts, decrypts, and parses all LLM API traffic with per-model token usage capture.
How It Works
Client → Proxy (8741) → Standalone LS (as antigravity-ls user)
↓ (port 443 traffic)
iptables REDIRECT (UID-scoped)
↓
MITM Proxy (8742)
↓ (TLS decrypt + parse SSE)
Google API (daily-cloudcode-pa.googleapis.com)
Components
-
UID-scoped iptables (
scripts/mitm-redirect.sh)- Creates
antigravity-lssystem user - iptables rule: redirect UID's port-443 → MITM port
- Only the standalone LS is affected — no side effects on other software
- Creates
-
Combined CA bundle (
src/standalone.rs)- Go's
SSL_CERT_FILEreplaces (not appends) the system trust store - Proxy concatenates system CAs + MITM CA →
/tmp/antigravity-mitm-combined-ca.pem - Set as
SSL_CERT_FILEon the standalone LS process
- Go's
-
sudo -uspawning (src/standalone.rs)- If
antigravity-lsuser exists, LS is spawned viasudo -n -u antigravity-ls - Env vars passed via
/usr/bin/env KEY=VALUEargs - Falls back to current user if the dedicated user doesn't exist
- If
-
Google SSE parser (
src/mitm/intercept.rs)- Parses
data: {"response": {"usageMetadata": {...}}}events - Extracts
promptTokenCount,candidatesTokenCount,thoughtsTokenCount - Handles both Google and Anthropic SSE formats
- Parses
-
Transparent proxy (
src/mitm/proxy.rs)- Detects iptables-redirected connections via TLS ClientHello SNI
- Terminates TLS with dynamically generated certs
- Forwards HTTP/1.1 requests upstream with real DNS resolution (
dig @8.8.8.8) - Chunked response detection for fast completion
What We Tried (Historical)
1. Extension Patch — detectAndUseProxy ✅ Still Active
Patches detectAndUseProxy=1 in the extension JS. Makes auxiliary traffic
(Unleash, etc.) honor HTTPS_PROXY. Harmless, still applied.
2. MITM Wrapper (mitm-wrapper.sh) ⚠️ Superseded
Sets env vars on the main LS process. Works for routing but the main LS's
LLM client ignores HTTPS_PROXY. Superseded by standalone mode.
3. iptables REDIRECT (All Traffic) ❌ Abandoned
Redirected ALL port-443 traffic. Caused redirect loops, broke other HTTPS traffic. Replaced by UID-scoped redirect.
4. DNS Redirect (/etc/hosts) ❌ Abandoned
Same TLS trust issue as #3. Unnecessary with UID-scoped iptables.
5. Standalone LS + UID-scoped iptables ✅ WORKING
Current solution. Full MITM interception with zero side effects.
The Original Blocker (SOLVED)
The LS's Go LLM HTTP client uses a custom
tls.Configthat does NOT read fromSSL_CERT_FILEor the system CA store.
This turned out to be wrong. The Go client DOES honor SSL_CERT_FILE when:
- The env var is set BEFORE the process starts (not injected later)
- The value contains a combined bundle (system CAs + custom CA)
SSL_CERT_DIRis set to/dev/nullto force exclusive use ofSSL_CERT_FILE
The standalone LS gives us full control over the process environment at spawn time, which is why this approach works while the wrapper approach didn't.
Technical Details
API Endpoint
POST https://daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse
SSE Response Format
data: {"response": {"candidates": [{"content": {"role": "model", "parts": [{"text": "..."}]}}],
"usageMetadata": {"promptTokenCount": 1514, "candidatesTokenCount": 25,
"totalTokenCount": 1539, "thoughtsTokenCount": 52},
"modelVersion": "gemini-3-flash"}, "traceId": "...", "metadata": {}}
Last event includes "finishReason": "STOP" in the candidate.
Other Intercepted Endpoints
| Endpoint | Type | Content |
|---|---|---|
fetchUserInfo |
Protobuf | User info |
loadCodeAssist |
Protobuf | Extension config |
fetchAvailableModels |
Protobuf | Model catalog |
webDocsOptions |
Protobuf | Docs config |
streamGenerateContent |
SSE/JSON | LLM responses ✅ |
recordCodeAssistMetrics |
Protobuf | Telemetry |
recordTrajectoryAnalytics |
Protobuf | Telemetry |
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 |
Setup
# One-time setup (creates user + iptables rule)
sudo ./scripts/mitm-redirect.sh install
# Run proxy with standalone LS + MITM
RUST_LOG=info ./target/release/antigravity-proxy --standalone
# Check usage
curl -s http://localhost:8741/v1/usage | jq .
Cleanup
# Remove iptables rule + user
sudo ./scripts/mitm-redirect.sh uninstall