Skip to content

Cloudflare Worker Proxy for the Dashboard

The ScamShield AI dashboard can be served through a Cloudflare Worker to provide a custom domain, SSL termination, caching, and DDoS protection.

Why Use a Proxy

Cloud Run generates URLs like scamshield-dashboard-xxxxx-el.a.run.app. A Cloudflare Worker maps your custom domain (e.g., scam.example.com) to the Cloud Run service while adding:

  • Custom domain with automatic SSL
  • Cloudflare's DDoS protection and WAF
  • Edge caching for static assets
  • Request/response header manipulation

Worker Setup

1. Create the Worker

In the Cloudflare dashboard:

  1. Go to Workers & Pages > Create application > Create Worker
  2. Name it your-cloudflare-worker-name
  3. Replace the default code with:
export default {
  async fetch(request) {
    const url = new URL(request.url);

    // Rewrite to Cloud Run backend
    url.hostname = "scamshield-dashboard-xxxxx-el.a.run.app";

    // Forward the request
    const response = await fetch(url.toString(), {
      method: request.method,
      headers: request.headers,
      body: request.body,
      redirect: "manual",  // CRITICAL: Do NOT change to "follow"
    });

    // Clone response and forward all headers
    const newResponse = new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
    });

    return newResponse;
  },
};

2. Add a Route

Bind the Worker to your custom domain:

  1. Go to Workers & Pages > your-cloudflare-worker-name > Triggers
  2. Add route: scam.example.com/*
  3. Select the zone for example.com

The Critical Gotcha: redirect: "manual"

Never change redirect to \"follow\"

The redirect: "manual" setting in the Worker's fetch() call is critical. Changing it to "follow" will break the dashboard.

Why this matters:

Streamlit uses HTTP 302 redirects for internal routing (page navigation, WebSocket upgrade negotiation). When redirect: "follow" is set:

  1. Streamlit returns a 302 Redirect to /_stcore/stream
  2. Cloudflare's Worker follows the redirect server-side
  3. The response body is returned to the client, but the client's URL does not change
  4. Streamlit's JavaScript client expects the redirect to happen client-side
  5. The WebSocket connection fails, and the dashboard shows a blank page or connection error

With redirect: "manual":

  1. Streamlit returns a 302 Redirect
  2. The Worker passes the 302 response directly to the browser
  3. The browser follows the redirect client-side (correct behavior)
  4. Streamlit's routing works normally

Cloudflare Settings That MUST Stay OFF

These Cloudflare features are incompatible with Streamlit and must be disabled for the zone or the specific route:

Rocket Loader

What it does: Defers loading of JavaScript by injecting a loader script.

Why it breaks Streamlit: Streamlit's frontend is a React SPA that depends on immediate JavaScript execution. Rocket Loader's deferred loading prevents the WebSocket connection from initializing, resulting in a blank page or infinite spinner.

How to disable: Zone dashboard > Speed > Optimization > Content Optimization > Rocket Loader > Off

Email Obfuscation

What it does: Rewrites @ symbols in page content to prevent email scraping bots.

Why it breaks Streamlit: Streamlit renders page content dynamically via JavaScript. Email obfuscation rewrites @ characters in the HTML source, corrupting UPI IDs (e.g., user@oksbi), email addresses, and other content containing @.

How to disable: Zone dashboard > Scrape Shield > Email Address Obfuscation > Off

Speed Brain (Speculative Preloading)

What it does: Prefetches pages the user might navigate to next.

Why it breaks Streamlit: Streamlit is a single-page application. Speculative preloading triggers additional requests that confuse Streamlit's server-side state management, causing page loads to fail or display incorrect content.

How to disable: Zone dashboard > Speed > Optimization > Speed Brain > Off

DNS Setup

Configure a DNS record pointing your custom domain to the Cloudflare Worker:

Type:    CNAME
Name:    scam
Target:  your-cloudflare-worker-name.<your-account>.workers.dev
Proxy:   Proxied (orange cloud)

Option B: Worker Route on Existing Domain

If the domain is already on Cloudflare, just add the Worker route (step 2 above). No additional DNS records are needed.

Testing the Proxy

1. Verify the Worker Responds

curl -sI https://scam.example.com/ | head -20

You should see:

  • HTTP/2 200 or HTTP/2 302 (Streamlit redirect)
  • server: cloudflare
  • cf-ray: ... header

2. Verify Streamlit Loads

Open https://scam.example.com/ in a browser. You should see the Streamlit login page (PIN entry).

3. Verify WebSocket Connection

Open browser DevTools > Network tab. After the page loads, you should see a WebSocket connection to /_stcore/stream with status 101 Switching Protocols.

If the WebSocket connection fails:

  1. Check redirect: "manual" in the Worker code
  2. Check that Rocket Loader is OFF
  3. Check browser console for errors

4. Test PIN Auth

Enter the PIN. After successful login, verify:

  • The scamshield_auth cookie is set
  • Refreshing the page does not require re-entering the PIN (cookie-based auth)

Updating the Worker

After editing the Worker code in the Cloudflare dashboard, click Save and Deploy. Changes take effect within seconds at Cloudflare's edge network.

Alternatively, use the Wrangler CLI:

npx wrangler deploy

Cloudflare Free Plan Limitations

The Cloudflare free plan supports Workers and Workers routes. The proxy setup described here works entirely on the free plan. Note that Origin Rules are a paid feature and are not used in this setup.