Headless Playwright browser behind OpenVPN for cookie-authenticated fetches from the TEE.
YouTube/Google tie auth cookies to the IP where the session was created. Raw fetch() from the TEE's datacenter IP returns logged_in: 0. A real browser with injected cookies through a VPN works.
Custom capability code
→ fetch('http://browser:3000/browse', {...})
→ Playwright (headless Chromium)
→ socks5://openvpn-socks5:1080
→ ProtonVPN → youtube.com
POST /browse
{
"url": "https://www.youtube.com/feed/history",
"cookies": [{"name": "SID", "value": "...", "domain": ".youtube.com", "path": "/", "secure": true, "httpOnly": false, "sameSite": "Lax"}],
"userAgent": "Mozilla/5.0 ...",
"script": "document.title"
}Returns { status, url, data } (with script) or { status, url, body } (without).
Each request gets a fresh browser context — no state leaks between requests.
GET /health — { ok: true, proxy: "socks5://..." }
Current: stateless Playwright — inject cookies per request, destroy context after.
Next: replace browser service with Envoy's Neko container for a persistent "teleport browser":
- Replace
browserDockerfile with Envoy'sneko/Dockerfile(Chromium + extension + ws-bridge) - Mount a profile volume for persistent logins (
/home/neko/.config/chromium) - Expose WebRTC port for visual access (debugging, manual auth)
- Extension bridge at
:3000replaces our/browseendpoint - Custom capabilities call
http://browser:3000/api/bridgeinstead of/browse - Keep
openvpn-socks5sidecar — Chromium launched with--proxy-server=socks5://openvpn-socks5:1080
Key files from envoy to crib:
neko/Dockerfile— Neko + extension + bridgeneko/ws-bridge.js— HTTP command bridge (extension polls, clients submit)neko/chromium.conf— supervisord config with proxy flagsextension/— Chrome extension for undetectable automationsrc/controller/bridge.ts— WebSocket client for scripting
The switch is: stateless (inject cookies) → persistent (stay logged in). Same VPN sidecar, same compose slot.