# cf-proxy — Cloudflare-tunnel-shape reverse proxy for the local harness. # # Production path: agent → CF tunnel → AWS LB → tenant container. # This config replays the same header rewrites the CF tunnel does so # the tenant sees the same Host + X-Forwarded-* it would in production. # # The tenant's TenantGuard middleware activates on MOLECULE_ORG_ID; the # canvas's same-origin fetches use the Host header for cookie scoping. # Both behave correctly in production because CF rewrites Host to the # tenant subdomain — this proxy reproduces that locally. # # How tests reach it: # curl --resolve 'harness-tenant.localhost:8443:127.0.0.1' \ # https://harness-tenant.localhost:8443/health # or via /etc/hosts (added automatically by ./up.sh on first boot). worker_processes 1; events { worker_connections 256; } http { # Map the wildcard .localhost to the tenant container. The # tenant container itself doesn't care which slug routed to it — # what matters is that the Host header it sees matches what # production's CF tunnel sets, so cookie/CORS/TenantGuard logic # exercises the same code path. server { listen 8080; server_name *.localhost localhost; # Cap upload at 50MB to mirror the staging tenant nginx limit; # chat upload tests will fail closed if the platform handler # ever silently expands its limit (catches the failure mode # opposite of the chat-files lazy-heal incident). client_max_body_size 50m; location / { proxy_pass http://tenant:8080; # Header parity with CF tunnel + AWS LB. Production CF sets # X-Forwarded-Proto=https; we keep http here because TLS # termination in compose is unnecessary for testing the # tenant logic — TLS is a CF concern, not a tenant bug # surface. If TLS-specific bugs ever bite, add cert-manager # + listen 8443 ssl here. proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; # Streamable HTTP / SSE / WebSocket — the tenant exposes /ws # and /events/stream + MCP /mcp/stream. Disabling buffering # reproduces CF tunnel's pass-through streaming semantics # (CF tunnel = no buffering by default; nginx default IS # buffering, which would mask issue #2397-class streaming # bugs by accumulating output until the client disconnects). proxy_buffering off; proxy_request_buffering off; proxy_http_version 1.1; proxy_set_header Connection ""; # Read timeout — CF tunnel default is 100s. Setting this to # the same value catches "long agent run finishes after the # proxy already closed the upstream" failure mode. proxy_read_timeout 100s; } } }