From e5bed8d204f94411c14b0ff602fd70bc7f8dc1d5 Mon Sep 17 00:00:00 2001 From: Clevin Canales Date: Sun, 10 May 2026 14:50:17 -0400 Subject: [PATCH] feat(serve-http): GBRAIN_TRUST_PROXY env var + Supabase deploy docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When deploying `gbrain serve --http` behind a PaaS load balancer (Fly.io, Render, Railway, Vercel), Express's default `trust proxy: 'loopback'` is too restrictive — the real client IP arrives in X-Forwarded-For after one hop, and rate limiting + `req.secure` detection both need that hop trusted. Adds GBRAIN_TRUST_PROXY env var. Default behavior unchanged ('loopback'). Set GBRAIN_TRUST_PROXY=1 for single-hop PaaS, or any other valid Express trust-proxy value (boolean string, IP list, function name, etc). Also adds a "Supabase Deployment Caveat" section to docs/mcp/DEPLOY.md covering the common gotchas for external Supabase-backed deployments: GBRAIN_DISABLE_DIRECT_POOL=1 (direct postgres host unreachable from outside Supabase VPC), GBRAIN_POOL_SIZE=1 (transaction-pooler safe sizing), pooler URL on port 6543 (not 5432), and a minimal Fly.io fly.toml example. Replaces #759 — that PR also bundled DCR rate limiter changes which landed independently in master (the v0.31 fix-wave subsumed them), so this is the focused remainder. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/mcp/DEPLOY.md | 51 ++++++++++++++++++++++++++++++++++++++ src/commands/serve-http.ts | 10 +++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/docs/mcp/DEPLOY.md b/docs/mcp/DEPLOY.md index 4f508bf35..308d50ff4 100644 --- a/docs/mcp/DEPLOY.md +++ b/docs/mcp/DEPLOY.md @@ -216,6 +216,57 @@ paths outside cwd are rejected. Page slugs and filenames are allowlist-validated CLI callers (`gbrain file upload ...`) keep unrestricted filesystem access since the user owns the machine. +## Supabase Deployment Caveat + +If your brain is backed by Supabase and you are deploying `gbrain serve --http` +to an external host (Fly.io, Render, Railway, your own VPS), set: + +```bash +GBRAIN_DISABLE_DIRECT_POOL=1 +GBRAIN_POOL_SIZE=1 +GBRAIN_TRUST_PROXY=1 +``` + +**Why `GBRAIN_DISABLE_DIRECT_POOL=1`:** GBrain's dual-pool routing tries to open +a direct connection to the Postgres host (port 5432) alongside the standard +pooler connection. On Supabase that direct host is only reachable from inside +Supabase's VPC — not accessible from external deployments. Without this flag the +startup sequence attempts an IPv6 connection to the direct host, times out (or +receives connection-refused), and either crashes or falls back to a degraded +state. + +**Why `GBRAIN_TRUST_PROXY=1`:** Express defaults to `trust proxy: 'loopback'` +which only trusts proxies on `127.0.0.1`. PaaS load balancers (Fly, Render, +Railway, Vercel) terminate TLS one network hop in front of your app, so the +real client IP arrives in `X-Forwarded-For` after **exactly 1 hop**. Setting +`GBRAIN_TRUST_PROXY=1` tells Express to trust that one hop, which is required +for both rate limiting (correct client IPs) and `req.secure` detection +(needed for OAuth flows that require HTTPS). + +**Connection string:** use the **Transaction Pooler** URL (port **6543**, not +5432): + +``` +postgresql://postgres.:@aws-0-.pooler.supabase.com:6543/ +``` + +The Session Pooler (port 5432 on the pooler host) also works but has higher +per-connection overhead. The **direct** connection string +(`db..supabase.co:5432`) will not work from outside the Supabase +VPC and should not be used for externally-deployed servers. + +**Minimal Fly.io `fly.toml` env block example:** + +```toml +[env] + GBRAIN_DISABLE_DIRECT_POOL = "1" + GBRAIN_POOL_SIZE = "1" + GBRAIN_TRUST_PROXY = "1" +``` + +Set `DATABASE_URL` as a secret (`fly secrets set DATABASE_URL=...`) — never +commit it to `fly.toml`. + ## Deployment Options See [ALTERNATIVES.md](ALTERNATIVES.md) for a comparison of ngrok, Tailscale diff --git a/src/commands/serve-http.ts b/src/commands/serve-http.ts index b8da94603..52e863891 100644 --- a/src/commands/serve-http.ts +++ b/src/commands/serve-http.ts @@ -217,7 +217,15 @@ export async function runServeHttp(engine: BrainEngine, options: ServeHttpOption // Express 5 app const app = express(); - app.set('trust proxy', 'loopback'); // Caddy/Tailscale reverse proxy on localhost + // Trust-proxy default is 'loopback' (works for local Caddy/Tailscale reverse proxies). + // Set GBRAIN_TRUST_PROXY=1 (or any positive integer) when deploying behind a PaaS + // load balancer (Fly.io, Render, Railway, Vercel) where the actual client IP + // arrives in X-Forwarded-For after exactly N hops. See docs/mcp/DEPLOY.md. + const trustProxyEnv = process.env.GBRAIN_TRUST_PROXY; + const trustProxyValue = trustProxyEnv + ? (/^\d+$/.test(trustProxyEnv) ? parseInt(trustProxyEnv, 10) : trustProxyEnv) + : 'loopback'; + app.set('trust proxy', trustProxyValue); // --------------------------------------------------------------------------- // Cookie parsing — required for /admin auth (express 5 has no built-in)