docs: add MITM interception research and redirect scripts
This commit is contained in:
242
docs/mitm-interception-status.md
Normal file
242
docs/mitm-interception-status.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# 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 's/detectAndUseProxy=pe.UNSPECIFIED/detectAndUseProxy=1/' \
|
||||
/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/<PID>/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 <PID>` (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
|
||||
74
docs/standalone-ls-todo.md
Normal file
74
docs/standalone-ls-todo.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Standalone LS for Proxy Isolation
|
||||
|
||||
## Goal
|
||||
|
||||
Route ALL proxy traffic through a standalone LS instance instead of the real one,
|
||||
so development/testing/proxying never interferes with active coding sessions.
|
||||
|
||||
## Current State
|
||||
|
||||
The proxy currently talks to the **real** LS spawned by Antigravity.
|
||||
This is risky — a bad cascade or proxy bug can disrupt the coding conversation.
|
||||
|
||||
## What Works
|
||||
|
||||
- Standalone LS starts fine with custom init metadata via stdin protobuf
|
||||
- Connects to the main extension server (`-extension_server_port`)
|
||||
- Accepts cascade requests (returns cascadeId)
|
||||
- With `detect_and_use_proxy = ENABLED` (field 34 = 2), honors `HTTPS_PROXY`
|
||||
|
||||
## What Doesn't Work
|
||||
|
||||
- **Cascades silently fail** — the LS accepts the request but never processes it
|
||||
- No planner invocation, no upstream API call, no logs beyond startup
|
||||
- 9 lines of log after 40s wait
|
||||
- Main LS logs show zero trace of the standalone's cascade
|
||||
|
||||
## Suspected Blockers (investigate in order)
|
||||
|
||||
1. **Auth context** — standalone may not receive OAuth token from extension server
|
||||
- Check: does the standalone's `GetUserStatus` return valid auth?
|
||||
- The extension server might only share tokens with the "primary" LS
|
||||
|
||||
2. **Unleash feature flags** — cascade processing gated by flags the standalone doesn't fetch
|
||||
- The standalone connects to Unleash via the proxy, but might not get the right flags
|
||||
- Check: compare Unleash responses between main and standalone
|
||||
|
||||
3. **Workspace indexing** — planner might require indexed workspace state
|
||||
- The standalone's workspace (`/tmp/antigravity-standalone`) is empty
|
||||
- Try: point it at a real workspace with actual files
|
||||
|
||||
4. **Extension server coupling** — cascade might need the extension to "drive" it
|
||||
- The chat panel in the extension might send additional RPCs to progress the cascade
|
||||
- Check: trace what RPCs the extension sends after StartCascade
|
||||
|
||||
## Investigation Plan
|
||||
|
||||
```bash
|
||||
# 1. Launch with max verbosity
|
||||
echo "$METADATA" | base64 -d | \
|
||||
timeout 90 "$LS_BIN" \
|
||||
-v 5 \
|
||||
-server_port 42200 \
|
||||
... > /tmp/standalone-verbose.log 2>&1 &
|
||||
|
||||
# 2. Check auth status
|
||||
curl -sk "https://127.0.0.1:42200/exa.language_server_pb.LanguageServerService/GetUserStatus" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-codeium-csrf-token: $CSRF" \
|
||||
-d '{}'
|
||||
|
||||
# 3. Send cascade and watch logs in real-time
|
||||
tail -f /tmp/standalone-verbose.log &
|
||||
curl -sk "https://127.0.0.1:42200/.../StartCascade" ...
|
||||
|
||||
# 4. Compare Unleash flags
|
||||
# Main LS unleash vs standalone unleash
|
||||
```
|
||||
|
||||
## Key Technical Details
|
||||
|
||||
- Init metadata protobuf field 34 = `detect_and_use_proxy` (enum: 0=UNSPECIFIED, 1=ENABLED, 2=DISABLED)
|
||||
- Model IDs: M18=Flash, M8=Pro-High, M7=Pro-Low, M26=Opus4.6, M12=Opus4.5
|
||||
- LS binary: `/usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64`
|
||||
- API endpoint: `daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse`
|
||||
163
scripts/dns-redirect.sh
Executable file
163
scripts/dns-redirect.sh
Executable file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env bash
|
||||
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
||||
# ║ Antigravity MITM — DNS-based redirect for targeted interception ║
|
||||
# ║ ║
|
||||
# ║ Instead of redirecting ALL port 443 traffic (which breaks everything), ║
|
||||
# ║ this uses /etc/hosts to redirect ONLY the LLM API domain to localhost, ║
|
||||
# ║ then iptables redirects only localhost:443 → MITM port. ║
|
||||
# ║ ║
|
||||
# ║ Also adds the MITM CA to the system trust store so Go trusts it. ║
|
||||
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
||||
set -euo pipefail
|
||||
|
||||
MITM_PORT="${ANTIGRAVITY_MITM_PORT:-8742}"
|
||||
MITM_CA="${HOME}/.config/antigravity-proxy/mitm-ca.pem"
|
||||
# If run with sudo, use SUDO_USER's home
|
||||
if [[ -n "${SUDO_USER:-}" ]]; then
|
||||
MITM_CA="$(eval echo "~${SUDO_USER}")/.config/antigravity-proxy/mitm-ca.pem"
|
||||
fi
|
||||
|
||||
HOSTS_MARKER="# antigravity-mitm"
|
||||
API_DOMAINS=(
|
||||
"daily-cloudcode-pa.googleapis.com"
|
||||
)
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
cmd_install() {
|
||||
echo -e "${BOLD}${CYAN}Antigravity MITM DNS Redirect Setup${NC}"
|
||||
echo -e "────────────────────────────────────"
|
||||
echo ""
|
||||
|
||||
# 1. Add MITM CA to system trust store
|
||||
if [[ ! -f "$MITM_CA" ]]; then
|
||||
echo -e " ${RED}✗${NC} MITM CA not found: ${MITM_CA}"
|
||||
echo -e " Start the proxy once first to generate it."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local sys_cert="/usr/local/share/ca-certificates/antigravity-mitm.crt"
|
||||
cp "$MITM_CA" "$sys_cert"
|
||||
update-ca-certificates >/dev/null 2>&1
|
||||
echo -e " ${GREEN}✓${NC} MITM CA added to system trust store"
|
||||
|
||||
# 2. Add /etc/hosts entries for API domains → 127.0.0.1
|
||||
# First, cache the real IPs for the MITM to use later
|
||||
local real_ips_file="/tmp/antigravity-mitm-real-ips"
|
||||
> "$real_ips_file"
|
||||
|
||||
for domain in "${API_DOMAINS[@]}"; do
|
||||
# Remove old entries
|
||||
sed -i "/${domain}.*${HOSTS_MARKER}/d" /etc/hosts
|
||||
|
||||
# Resolve and cache the real IPs BEFORE redirecting
|
||||
local real_ip
|
||||
real_ip=$(dig +short "$domain" 2>/dev/null | grep -E '^[0-9]+\.' | head -1)
|
||||
if [[ -n "$real_ip" ]]; then
|
||||
echo "${domain}=${real_ip}" >> "$real_ips_file"
|
||||
fi
|
||||
|
||||
# Add the /etc/hosts redirect
|
||||
echo "127.0.0.1 ${domain} ${HOSTS_MARKER}" >> /etc/hosts
|
||||
echo -e " ${GREEN}✓${NC} /etc/hosts: ${domain} → 127.0.0.1 (real: ${real_ip:-unknown})"
|
||||
done
|
||||
|
||||
# 3. iptables: redirect ONLY 127.0.0.1:443 → MITM port
|
||||
# This catches only the /etc/hosts redirected domains, nothing else!
|
||||
iptables -t nat -D OUTPUT -d 127.0.0.1 -p tcp --dport 443 \
|
||||
-j REDIRECT --to-port "$MITM_PORT" 2>/dev/null || true
|
||||
iptables -t nat -A OUTPUT -d 127.0.0.1 -p tcp --dport 443 \
|
||||
-j REDIRECT --to-port "$MITM_PORT"
|
||||
echo -e " ${GREEN}✓${NC} iptables: 127.0.0.1:443 → localhost:${MITM_PORT}"
|
||||
|
||||
echo ""
|
||||
echo -e " ${GREEN}Done!${NC}"
|
||||
echo ""
|
||||
echo -e " ${BOLD}How it works:${NC}"
|
||||
echo -e " 1. LS resolves ${API_DOMAINS[0]} → 127.0.0.1 (via /etc/hosts)"
|
||||
echo -e " 2. LS connects to 127.0.0.1:443"
|
||||
echo -e " 3. iptables redirects to MITM proxy on :${MITM_PORT}"
|
||||
echo -e " 4. MITM intercepts, decrypts (CA is trusted), proxies to real Google"
|
||||
echo ""
|
||||
echo -e " Real upstream IPs cached in: ${real_ips_file}"
|
||||
echo -e " Restart Antigravity for changes to take effect."
|
||||
echo -e " Undo: sudo $0 uninstall"
|
||||
echo ""
|
||||
}
|
||||
|
||||
cmd_uninstall() {
|
||||
echo -e "${BOLD}${CYAN}Removing MITM DNS Redirect${NC}"
|
||||
echo ""
|
||||
|
||||
# Remove /etc/hosts entries
|
||||
sed -i "/${HOSTS_MARKER}/d" /etc/hosts
|
||||
echo -e " ${GREEN}✓${NC} Removed /etc/hosts entries"
|
||||
|
||||
# Remove iptables rule
|
||||
iptables -t nat -D OUTPUT -d 127.0.0.1 -p tcp --dport 443 \
|
||||
-j REDIRECT --to-port "$MITM_PORT" 2>/dev/null || true
|
||||
echo -e " ${GREEN}✓${NC} Removed iptables rule"
|
||||
|
||||
# Remove system CA (optional)
|
||||
rm -f /usr/local/share/ca-certificates/antigravity-mitm.crt
|
||||
update-ca-certificates >/dev/null 2>&1
|
||||
echo -e " ${GREEN}✓${NC} Removed MITM CA from system trust store"
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
echo -e "${BOLD}${CYAN}MITM DNS Redirect Status${NC}"
|
||||
echo ""
|
||||
|
||||
# Check /etc/hosts
|
||||
local hosts_count
|
||||
hosts_count=$(grep -c "$HOSTS_MARKER" /etc/hosts 2>/dev/null || echo 0)
|
||||
if [[ "$hosts_count" -gt 0 ]]; then
|
||||
echo -e " ${GREEN}✓${NC} /etc/hosts: ${hosts_count} domain(s) redirected"
|
||||
grep "$HOSTS_MARKER" /etc/hosts | sed 's/^/ /'
|
||||
else
|
||||
echo -e " ${YELLOW}○${NC} /etc/hosts: no redirects"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
# Check iptables
|
||||
if iptables -t nat -L OUTPUT -n 2>/dev/null | grep -q "127.0.0.1.*REDIRECT.*${MITM_PORT}"; then
|
||||
echo -e " ${GREEN}✓${NC} iptables: 127.0.0.1:443 → :${MITM_PORT}"
|
||||
else
|
||||
echo -e " ${YELLOW}○${NC} iptables: no redirect"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
# Check system CA
|
||||
if [[ -f /usr/local/share/ca-certificates/antigravity-mitm.crt ]]; then
|
||||
echo -e " ${GREEN}✓${NC} System CA: installed"
|
||||
else
|
||||
echo -e " ${YELLOW}○${NC} System CA: not installed"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install)
|
||||
cmd_install
|
||||
;;
|
||||
uninstall)
|
||||
cmd_uninstall
|
||||
;;
|
||||
status)
|
||||
cmd_status
|
||||
;;
|
||||
*)
|
||||
echo "Usage: sudo $0 {install|uninstall|status}"
|
||||
echo ""
|
||||
echo "Redirects LLM API domain to localhost via /etc/hosts + iptables."
|
||||
echo "Only intercepts API traffic, everything else is untouched."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
168
scripts/iptables-redirect.sh
Executable file
168
scripts/iptables-redirect.sh
Executable file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env bash
|
||||
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
||||
# ║ Antigravity MITM — iptables redirect for transparent interception ║
|
||||
# ║ ║
|
||||
# ║ Redirects outbound port 443 traffic to the MITM proxy. ║
|
||||
# ║ Uses a dedicated GID to exclude the proxy's own upstream traffic, ║
|
||||
# ║ preventing redirect loops. ║
|
||||
# ║ ║
|
||||
# ║ Usage: sudo ./iptables-redirect.sh install ║
|
||||
# ║ sudo ./iptables-redirect.sh uninstall ║
|
||||
# ║ sudo ./iptables-redirect.sh status ║
|
||||
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
||||
set -euo pipefail
|
||||
|
||||
MITM_PORT="${ANTIGRAVITY_MITM_PORT:-8742}"
|
||||
CHAIN="ANTIGRAVITY_MITM"
|
||||
BYPASS_GROUP="mitm-bypass"
|
||||
|
||||
# Resolve target user (the one whose traffic we redirect)
|
||||
TARGET_USER="${SUDO_USER:-$(whoami)}"
|
||||
TARGET_UID=$(id -u "$TARGET_USER" 2>/dev/null || echo "")
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
cmd_install() {
|
||||
echo -e "${BOLD}${CYAN}Antigravity MITM iptables Setup${NC}"
|
||||
echo -e "────────────────────────────────"
|
||||
echo ""
|
||||
|
||||
if [[ -z "$TARGET_UID" ]]; then
|
||||
echo -e " ${RED}✗${NC} Cannot resolve UID for user '${TARGET_USER}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create bypass group (proxy runs with this GID to avoid redirect loop)
|
||||
if ! getent group "$BYPASS_GROUP" >/dev/null 2>&1; then
|
||||
groupadd "$BYPASS_GROUP"
|
||||
echo -e " ${GREEN}✓${NC} Created group: ${BYPASS_GROUP}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Group exists: ${BYPASS_GROUP}"
|
||||
fi
|
||||
|
||||
# Add user to bypass group (so they can use 'sg' to run proxy)
|
||||
if ! id -nG "$TARGET_USER" 2>/dev/null | grep -qw "$BYPASS_GROUP"; then
|
||||
usermod -aG "$BYPASS_GROUP" "$TARGET_USER"
|
||||
echo -e " ${GREEN}✓${NC} Added ${TARGET_USER} to ${BYPASS_GROUP}"
|
||||
fi
|
||||
|
||||
local bypass_gid
|
||||
bypass_gid=$(getent group "$BYPASS_GROUP" | cut -d: -f3)
|
||||
|
||||
# Check MITM proxy is running
|
||||
if ! ss -tlnp 2>/dev/null | grep -q ":${MITM_PORT}"; then
|
||||
echo -e " ${YELLOW}!${NC} MITM proxy not running on :${MITM_PORT} (will work once started)"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} MITM proxy listening on :${MITM_PORT}"
|
||||
fi
|
||||
|
||||
# Create our chain
|
||||
iptables -t nat -N "$CHAIN" 2>/dev/null || true
|
||||
iptables -t nat -F "$CHAIN"
|
||||
|
||||
# THE KEY RULE: redirect port 443 traffic UNLESS it's from the bypass group.
|
||||
# This prevents redirect loops — the proxy runs with GID=mitm-bypass,
|
||||
# so its upstream connections to Google are NOT redirected back to itself.
|
||||
iptables -t nat -A "$CHAIN" \
|
||||
-m owner ! --gid-owner "$bypass_gid" \
|
||||
-p tcp --dport 443 \
|
||||
-j REDIRECT --to-port "$MITM_PORT"
|
||||
|
||||
echo -e " ${GREEN}✓${NC} Redirect rule: tcp/443 → :${MITM_PORT} (skip GID ${bypass_gid})"
|
||||
|
||||
# Hook into OUTPUT for target user only
|
||||
iptables -t nat -D OUTPUT -m owner --uid-owner "$TARGET_UID" \
|
||||
-p tcp --dport 443 -j "$CHAIN" 2>/dev/null || true
|
||||
iptables -t nat -A OUTPUT -m owner --uid-owner "$TARGET_UID" \
|
||||
-p tcp --dport 443 -j "$CHAIN"
|
||||
|
||||
echo -e " ${GREEN}✓${NC} OUTPUT hook: UID ${TARGET_UID} (${TARGET_USER})"
|
||||
|
||||
echo ""
|
||||
echo -e " ${GREEN}Done!${NC}"
|
||||
echo ""
|
||||
echo -e " ${BOLD}IMPORTANT:${NC} Run the proxy with the bypass group to avoid loops:"
|
||||
echo -e " ${CYAN}sg ${BYPASS_GROUP} -c 'RUST_LOG=info ./target/release/antigravity-proxy'${NC}"
|
||||
echo ""
|
||||
echo -e " Then restart Antigravity to re-establish connections."
|
||||
echo -e " Undo: sudo $0 uninstall"
|
||||
echo ""
|
||||
}
|
||||
|
||||
cmd_uninstall() {
|
||||
echo -e "${BOLD}${CYAN}Removing iptables MITM redirect${NC}"
|
||||
echo ""
|
||||
|
||||
local target_uid
|
||||
target_uid=$(id -u "$TARGET_USER" 2>/dev/null || echo "1000")
|
||||
|
||||
# Remove jump from OUTPUT
|
||||
iptables -t nat -D OUTPUT -m owner --uid-owner "$target_uid" \
|
||||
-p tcp --dport 443 -j "$CHAIN" 2>/dev/null || true
|
||||
echo -e " ${GREEN}✓${NC} Removed OUTPUT jump"
|
||||
|
||||
# Flush and delete our chain
|
||||
iptables -t nat -F "$CHAIN" 2>/dev/null || true
|
||||
iptables -t nat -X "$CHAIN" 2>/dev/null || true
|
||||
echo -e " ${GREEN}✓${NC} Removed ${CHAIN} chain"
|
||||
|
||||
echo ""
|
||||
echo -e " ${YELLOW}Note:${NC} Group '${BYPASS_GROUP}' left intact (harmless)."
|
||||
echo -e " Remove with: sudo groupdel ${BYPASS_GROUP}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
echo -e "${BOLD}${CYAN}iptables MITM Status${NC}"
|
||||
echo ""
|
||||
|
||||
if iptables -t nat -L "$CHAIN" -n 2>/dev/null | grep -q "REDIRECT"; then
|
||||
echo -e " ${GREEN}✓${NC} Chain ${CHAIN}: active"
|
||||
iptables -t nat -L "$CHAIN" -nv --line-numbers 2>/dev/null | \
|
||||
sed 's/^/ /'
|
||||
else
|
||||
echo -e " ${YELLOW}○${NC} Chain ${CHAIN}: not installed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if iptables -t nat -L OUTPUT -n 2>/dev/null | grep -q "$CHAIN"; then
|
||||
echo -e " ${GREEN}✓${NC} OUTPUT hook: installed"
|
||||
iptables -t nat -L OUTPUT -n 2>/dev/null | grep "$CHAIN" | sed 's/^/ /'
|
||||
else
|
||||
echo -e " ${YELLOW}○${NC} OUTPUT hook: not installed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if getent group "$BYPASS_GROUP" >/dev/null 2>&1; then
|
||||
local gid
|
||||
gid=$(getent group "$BYPASS_GROUP" | cut -d: -f3)
|
||||
echo -e " ${GREEN}✓${NC} Bypass group: ${BYPASS_GROUP} (GID ${gid})"
|
||||
else
|
||||
echo -e " ${YELLOW}○${NC} Bypass group: not created"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install)
|
||||
cmd_install
|
||||
;;
|
||||
uninstall)
|
||||
cmd_uninstall
|
||||
;;
|
||||
status)
|
||||
cmd_status
|
||||
;;
|
||||
*)
|
||||
echo "Usage: sudo $0 {install|uninstall|status}"
|
||||
echo ""
|
||||
echo "Redirects outbound port 443 traffic to the MITM proxy."
|
||||
echo "The proxy must be run with 'sg mitm-bypass' to avoid redirect loops."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user