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.getAllCookiesreturns cookies for all domains, not just the page's - Tracker classification - analytics, advertising, social, and session-replay vendors
- Verdict -
clean,some-trackers, orheavy-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 trackerssome-trackers- 1–4 distinct trackers and fewer than 10 third-party cookiesheavy-tracking- 5+ distinct trackers or 10+ third-party cookies
Error Codes
400- Missing or invalidurl(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.tsuses 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.tsmatches 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 -
networkidle2with 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.comandturner.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.
npm install @cloudflare/puppeteerimport 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
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 devcurl "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