Skip to content

Latest commit

 

History

History
183 lines (148 loc) · 8.46 KB

File metadata and controls

183 lines (148 loc) · 8.46 KB

Exit node — bypassing CF anti-bot for ChatGPT / Claude / Grok / X

Many Cloudflare-fronted services flag traffic from Google datacenter IPs as bots and serve a Turnstile / CAPTCHA / 502 challenge instead of the real page. UrlFetchApp.fetch() in Apps Script always exits from Google's datacenter IP space, so for sites like:

  • chatgpt.com / openai.com
  • claude.ai
  • grok.com / x.com

…mhrv-rs's normal apps_script-mode path returns errors like Relay error: json: key must be a string at line 2 column 1 or 502 Relay error because Code.gs is wrapping a CF challenge HTML page that the client can't parse as relay JSON.

The exit node is a small TypeScript HTTP handler you deploy on a serverless TypeScript host you control. It sits between Apps Script and the destination, so the request chain becomes:

Browser ─┐                                                ┌─→ Destination
         │                                                │   (chatgpt.com)
         ▼                                                │
    mhrv-rs                                               │
       │                                                  │
       │  TLS to Google IP, SNI=www.google.com (DPI cover)│
       ▼                                                  │
   Apps Script (Google datacenter)                        │
       │                                                  │
       │  UrlFetchApp.fetch(EXIT_NODE_URL)                │
       ▼                                                  │
    your exit node (non-Google IP)                        │
       │                                                  │
       │  fetch(real_url)                                 │
       └──────────────────────────────────────────────────┘

The destination sees the exit node's outbound IP, not a Google datacenter IP. CF's anti-bot heuristic doesn't fire and the real page comes back.

Important property preserved: the user-side leg (Iran ISP → Apps Script) is unchanged. The ISP only sees TLS to a Google IP — the second hop happens entirely inside Apps Script's outbound, invisible from the user's network. The DPI evasion property mhrv-rs is built around stays intact.

Setup

The handler in exit_node.ts is plain TypeScript that uses only web-standard APIs (Request, Response, fetch). It runs on any platform with a serverless-fetch runtime.

Generic steps (apply to every host)

  1. Open exit_node.ts and replace the placeholder PSK at the top:
    const PSK = "<your-strong-secret>";
    Generate a strong secret with openssl rand -hex 32. Do not leave the placeholder — the script is deliberately fail-closed (returns 503 on every request until the placeholder is replaced) so a fresh deploy without configuration can't accidentally serve as an open relay.
  2. Deploy to your chosen host (see options below).
  3. Copy the public URL of the deployed handler.
  4. In mhrv-rs config.json, add an exit_node block:
    "exit_node": {
      "enabled": true,
      "relay_url": "https://your-deployed-exit-node.example.com",
      "psk": "<the same PSK you set in step 1>",
      "mode": "selective",
      "hosts": ["chatgpt.com", "claude.ai", "x.com", "grok.com", "openai.com"]
    }
  5. Restart mhrv-rs (Disconnect + Connect, or kill + restart the binary).
  6. Test — open chatgpt.com or grok.com from a browser pointed at mhrv-rs's proxy. You should see the real login page, not a CF challenge.

A complete example config is at config.exit-node.example.json in the repo root.

Hosting options

The script is one self-contained file. Pick whichever host you can sign up for and trust:

Host Notes
Deno Deploy (deno.com/deploy) Free tier covers personal use. Deploy via deployctl deploy --prod exit_node.ts or via GitHub Actions. Same web-standard API as the script expects.
fly.io Free tier with limits. Wrap the handler in a thin server (Deno.serve(handler) for Deno or an Express wrapper for Node) + add a Dockerfile. Persistent IPs, picks geographic region.
Your own VPS Run deno run --allow-net wrapper.ts where wrapper.ts does Deno.serve({ port: 8443 }, handler). Most control, ~$3-5/mo.
Cloudflare Workers Doesn't help. CF Workers exit through CF's own IP space, which CF anti-bot still flags as worker-internal traffic.

For most users running locally, Deno Deploy is the fastest setup. For a long-term deployment you control end-to-end, your own small VPS is ideal.

selective vs full

Mode What it does When to use
selective (default) Only hosts in hosts route via the exit node; everything else takes the normal Apps Script path Recommended. The exit-node hop adds ~200-500ms per request, so reserve it for sites that actually need a non-Google IP.
full Every request routes via the exit node Only when your entire workload is CF-anti-bot affected, or when your exit node is faster than Apps Script on your network path (rare). Burns the exit node's runtime budget on sites that don't need it.

Behaviour on failure

If the exit node is unreachable, returns 5xx, or returns a malformed response, mhrv-rs automatically falls back to the regular Apps Script relay. The log shows a warn: exit node failed for ... — falling back to direct Apps Script line. The CF-affected sites then fail (CF challenge), but every other site keeps working — a downed exit node doesn't take you fully offline.

Security model

The PSK is the only thing keeping the deployed endpoint from being a public open proxy. Treat it like a password:

  • Don't commit the PSK to source control. Most TypeScript hosts default deployed code to private; keep it that way.
  • Don't share publicly. Anyone with both the URL and the PSK can use the deployment as their own proxy and burn your runtime quota.
  • Rotate if you suspect a leak. Change the PSK in the deployed source, redeploy, then update psk in mhrv-rs config.json and restart.

The script also includes a loop guard (refuses to fetch its own host) and a placeholder check (returns 503 if PSK === "CHANGE_ME_TO_A_STRONG_SECRET") so a fresh deploy without configuration can't be accidentally served as an open relay.

Why isn't this on by default?

  • Adds ~200-500ms per request through the exit-node hop
  • Burns the host's free-tier runtime quota
  • No benefit for sites that don't have CF anti-bot
  • Requires signing up for a separate third-party platform

So enabled: false is the default. Users who specifically need ChatGPT / Claude / Grok opt in; everyone else runs lighter.

Troubleshooting

exit node refused or errored: unauthorized — PSK mismatch. Double-check psk in config.json matches the PSK constant in your deployed source character-for-character. Whitespace and quoting matter.

exit_node misconfigured: PSK is still the placeholder — you forgot to replace CHANGE_ME_TO_A_STRONG_SECRET in the source. Edit the deployed file, save, redeploy.

exit node failed for ...: connection refused — the URL is wrong or the deployment isn't live. Verify by hitting the URL in a browser — it should respond with {"e":"method_not_allowed"} (the script expects POST).

exit node failed for ...: timeout — the host's outbound or the destination is slow. Try a different region, or accept the latency trade-off.

Site still shows a CF challenge after enabling the exit node — CF has flagged your host's IP too. Some hosting providers' outbound IP space is on CF's bot blocklist. Workarounds: try a different host (your own VPS gives you a clean IP), or add the affected site to passthrough_hosts to bypass the MITM and use your real ISP IP.

See also