This site is not affiliated with or endorsed by Cloudflare, Inc. It showcases deployable experiments across most Cloudflare products.
Browser Rendering

Privacy Tracker Scanner

Load a page without accepting consent and report third-party trackers with Browser Rendering - privacy and CMP audit tool

See what a page does to a first-time visitor before they click anything. The Worker loads the URL in Browser Rendering, never touches the consent banner, records every network request and cookie, and classifies third-party hosts against a built-in tracker list.

Features

  • Pre-consent capture - no clicks, no scrolling; just what loads on arrival
  • Every request recorded - page.on("request") across the main frame and iframes
  • Full cookie jar - CDP Network.getAllCookies returns cookies for all domains, not just the page's
  • Tracker classification - analytics, advertising, social, and session-replay vendors
  • Verdict - clean, some-trackers, or heavy-tracking

API Reference

GET /scan

Prop

Type

Example Request

curl "https://your-worker.workers.dev/scan?url=https://www.cnn.com"

Success Response

{
  "url": "https://www.cnn.com/",
  "finalUrl": "https://www.cnn.com/",
  "firstPartyDomain": "cnn.com",
  "timedOut": true,
  "requests": { "total": 336, "thirdParty": 250 },
  "thirdPartyDomains": [
    {
      "domain": "securepubads.g.doubleclick.net",
      "requests": 9,
      "tracker": { "name": "Google DoubleClick", "category": "advertising" }
    },
    { "domain": "media.cnn.com", "requests": 1 }
  ],
  "trackers": [
    {
      "name": "Google DoubleClick",
      "category": "advertising",
      "domains": ["securepubads.g.doubleclick.net"],
      "requests": 15
    }
  ],
  "cookies": {
    "firstParty": 57,
    "thirdParty": 3,
    "list": [
      {
        "name": "IDE",
        "domain": ".doubleclick.net",
        "expires": "2027-10-26T12:00:00.000Z",
        "secure": true,
        "httpOnly": true,
        "sameSite": "None",
        "thirdParty": true
      }
    ]
  },
  "verdict": "heavy-tracking"
}

firstPartyDomain string

Registrable domain of the final page URL (after redirects).

thirdPartyDomains array

Every third-party hostname with its request count, sorted by requests. tracker is present when the host matches the tracker list.

trackers array

Matched trackers aggregated by vendor: name, category (analytics | advertising | social | session-replay), matching domains, and total requests.

cookies.list[].expires string | null

ISO timestamp, or null for session cookies.

timedOut boolean

true when the page didn't reach network idle within 15 seconds. The report covers everything that loaded until then.

verdict string

  • clean - no known trackers
  • some-trackers - 1–4 distinct trackers and fewer than 10 third-party cookies
  • heavy-tracking - 5+ distinct trackers or 10+ third-party cookies

Error Codes

  • 400 - Missing or invalid url (INVALID_URL)
  • 502 - Browser launch or navigation failed (BROWSER_ERROR)

Implementation Details

  • Third party - a host is third party when its registrable domain differs from the page's. src/lib/domain.ts uses an eTLD+1 heuristic: the last two labels, or three when the suffix is a known two-part TLD (co.uk, com.au, co.jp, …). It is not a full Public Suffix List lookup.
  • Tracker list - src/constants/trackers.ts matches hostname suffixes, and the most specific entry wins (connect.facebook.net → Facebook Pixel, www.facebook.com → Facebook). Includes Google Analytics, Tag Manager, DoubleClick, AdSense, Facebook Pixel, Hotjar, Microsoft Clarity, FullStory, Segment, Mixpanel, Amplitude, TikTok Pixel, LinkedIn Insight, X Ads, Criteo, Taboola, Outbrain, and more.
  • Navigation - networkidle2 with a 15 s cap. On timeout the scan keeps the partial capture instead of failing.

Use Cases

  • Check whether a site loads ad or analytics tags before consent (GDPR / ePrivacy audits)
  • Compare tracker footprints between competitors or releases
  • Verify that a consent management platform actually blocks tags
  • Learn request interception and CDP sessions in Browser Rendering

Limitations

  • Built-in tracker list is intentionally small; unknown vendors appear only in thirdPartyDomains
  • Registrable-domain detection is heuristic and can misjudge uncommon multi-part TLDs
  • Some sites block headless browsers or show a different page to bots
  • One visit from Cloudflare's network; geo-targeted consent behavior may differ from your region
  • Same-company domains (e.g. cnn.com and turner.com) count as third party

Use in your project

Copy these files into an existing Worker. Prefer Deployment to try the full experiment first. Source: apps/experiments/privacy-tracker-scanner.

Dependencies@cloudflare/puppeteerBindingsBrowser RenderingPlatformWorkers runtime
Install
npm install @cloudflare/puppeteer
import puppeteer from "@cloudflare/puppeteer";import {  DEFAULT_VIEWPORT,  MAX_RECORDED_REQUESTS,  NAVIGATION_TIMEOUT_MS,} from "../constants/defaults";import type { PageCapture } from "../types/scan";function isTimeoutError(e: unknown): boolean {  return e instanceof Error && (e.name === "TimeoutError" || /timeout/i.test(e.message));}/** * Loads `url` without clicking anything (consent banners stay untouched), recording every * request and every cookie in the browser jar (all frames, all domains). */export async function capturePage(browserBinding: Fetcher, url: string): Promise<PageCapture> {  let browser: Awaited<ReturnType<typeof puppeteer.launch>> | null = null;  try {    browser = await puppeteer.launch(browserBinding);    const page = await browser.newPage();    await page.setViewport(DEFAULT_VIEWPORT);    const requestUrls: string[] = [];    page.on("request", (request) => {      if (requestUrls.length < MAX_RECORDED_REQUESTS) requestUrls.push(request.url());    });    let timedOut = false;    try {      await page.goto(url, { waitUntil: "networkidle2", timeout: NAVIGATION_TIMEOUT_MS });    } catch (e) {      // Pages with long-polling/beacons never go idle; keep what loaded within the cap.      if (!isTimeoutError(e) || requestUrls.length === 0) throw e;      timedOut = true;    }    const client = await page.createCDPSession();    const { cookies } = await client.send("Network.getAllCookies");    return {      finalUrl: page.url(),      requestUrls,      cookies: cookies.map((cookie) => ({        name: cookie.name,        domain: cookie.domain,        expires: cookie.expires,        secure: cookie.secure,        httpOnly: cookie.httpOnly,        sameSite: cookie.sameSite,      })),      timedOut,    };  } finally {    if (browser) {      try {        await browser.close();      } catch {        /* ignore close errors */      }    }  }}

Deployment

Click the deploy button

Deploy to Cloudflare Workers

Enable Browser Rendering

The BROWSER binding in wrangler.json requires Browser Rendering on your account. See the Browser Rendering docs.

Test your deployment

curl "https://your-worker.workers.dev/scan?url=https://example.com"

Local Development

cd apps/experiments/privacy-tracker-scanner
npm install
npm run dev
curl "http://localhost:8787/scan?url=https://example.com"

Cloudflare Features Used

  • Workers - Edge compute runtime
  • Browser Rendering - Headless Chromium via @cloudflare/puppeteer, request events, and a CDP session for cookies

Next Steps

On this page