---
title: "Audit your cookie banner with AI: ConsentTheater + Claude + Zest"
description: "Most cookie banners leak before consent. Here is the 10-minute, $0 workflow to scan your site with ConsentTheater, hand the trace to Claude, and ship a compliant config with Zest."
author: "Alex Zappa"
date: "2026-05-19T12:00:00.000Z"
url: "https://freshjuice.dev/blog/audit-your-cookie-banner-with-ai/"
---
# Audit your cookie banner with AI: ConsentTheater + Claude + Zest

May 19, 2026 • Written by [Alex Zappa](https://freshjuice.dev/authors/reatlat/)

Most cookie banners are theater. A bit of CSS, a fat “Accept All” button, and underneath, Google Analytics is already on its third pageview. We covered the legal side of this in [Your cookie banner is probably illegal](https://freshjuice.dev/blog/your-cookie-banner-is-probably-illegal/) and the full playbook in [the 101 guide](https://freshjuice.dev/blog/how-to-deal-with-cookie-consent-in-your-site-a-101-guide/).

This post is the missing piece. The actual workflow.

Here is the workflow. It takes 10 minutes. It costs nothing. It runs on three things you can install right now:

1.  **[ConsentTheater](https://consenttheater.org/extension/?utm_source=freshjuice.dev)** to scan your site and tell you exactly what is leaking.
2.  **Claude (or ChatGPT)** to read that scan and write you a config.
3.  **[Zest](https://zest.freshjuice.dev/)**, our free open-source consent toolkit, to enforce that config in one `<script>` tag.

No subscription. No vendor lock-in. No “schedule a call”. Zip. Zero. Nada!

## Why this workflow exists

Cookie consent has a tooling problem.

The big CMPs charge $600 to $5,000 a year to do something a 16KB script can do for free. They sell “auto-scanning” as a feature, then misclassify Google Analytics as “essential” because misclassifying it makes their dashboard look greener. We literally saw [CookieScan do this on their own marketing site](https://freshjuice.dev/blog/how-to-deal-with-cookie-consent-in-your-site-a-101-guide/).

The free open-source CMPs are great, but they expect you to already know your stack. Which cookies fire. Which third parties phone home. Which goes in the “analytics” bucket and which goes in “marketing”. Most site owners have no idea, because they did not put those trackers there. WordPress did. HubSpot did. Some plugin from 2019 did.

That gap is where AI is actually useful. Not for writing the code. For reading the receipts and producing the config.

## The 10-minute workflow

### Step 1: Scan your site with ConsentTheater (2 minutes)

[ConsentTheater](https://consenttheater.org/extension/?utm_source=freshjuice.dev) is an open-source browser extension built by the privacy research community. No company behind it. No subscription. No incentive to call your trackers “essential” so a dashboard looks greener. Just researchers shipping the audit tool the consent industry refused to build. Nothing gets installed on your site. It runs entirely in your browser, loads your page in a fresh session, watches every network request and every cookie set, and flags anything that fires before consent is given.

Add it to Chrome, Firefox, or Edge from the extension store. Open your site. Click the ConsentTheater icon in your browser toolbar to open the sidebar, then click **Start test**. The extension wipes cookies and storage before the scan, so a regular window is fine. Wait for the audit to finish.

You get three export options:

-   **A human-readable PDF report** for your own records (or to send to a stakeholder who needs receipts).
-   **Copy report for AI** to clipboard, a compact text version pre-formatted for an LLM.
-   **A raw HAR file** of every network request, downloadable as `.har`.

For the AI step, the second option is what you want most of the time. It is already structured for a model, so you paste it straight into the prompt and go. Use the HAR when the site is messy (CMS + 6 marketing tools + a chatbot) and you want the model to see every request, header, and response. The PDF is for humans, not for Claude.

> **Sanity check before you go further:**  
> if ConsentTheater shows zero non-essential cookies and zero pre-consent requests, your banner already works. Close the tab. Have a coffee. You are done.
> 
> If it shows even one tracker firing before consent, keep reading.

### Step 2: Hand the trace to Claude (3 minutes)

Open [Claude](https://claude.ai/?utm_source=freshjuice.dev), ChatGPT, or whatever AI you trust. Paste the prompt below. Then paste your ConsentTheater report or attach the HAR file.

The prompt is the work. The model is just the executor.

```
You are a privacy engineer auditing a website for GDPR + ePrivacy + CCPA compliance.

Below is a ConsentTheater report (or HAR network trace) from my site: [YOUR DOMAIN]
Stack: [WordPress / HubSpot / Astro / custom / unknown]

References (if you can fetch URLs, load these first for context):
- https://consenttheater.org/methodology.md for ConsentTheater's audit methodology
  and classification taxonomy (what counts as pre-consent leak, exempt vs
  non-exempt, "umbrella" matches for regional CDNs, data_leak category).
- https://consenttheater.org/law.md for plain-language renderings of every GDPR
  and ePrivacy article ConsentTheater cites, with the verbatim official text
  alongside (Recital 30, Article 5(3), Schrems II implications, CCPA opt-out).
- https://consenttheater.org/llms.txt for the full handbook index (topic
  articles on strict-necessary cookies, tracking pixels, GPC, DNS sinkholes,
  privacy-friendly browsers) if you need deeper context for an edge case.
- https://zest.freshjuice.dev/docs/configuration.md for the Zest configuration schema.

Your job:

1. List every non-essential cookie, localStorage key, sessionStorage key,
   and third-party HTTP request that fired BEFORE consent was given.

2. For each, classify it as:
   - essential   (strictly necessary AND user-actively-chosen UI preferences:
                  session, CSRF, load balancing, the consent cookie itself,
                  theme / language / contrast / font-size that the user clicked
                  a control to set. If the user explicitly toggled it, storing
                  it is "necessary for the service the user requested" and
                  exempt from consent under GDPR + ePrivacy.)
   - functional  (auto-detected preferences they did NOT actively choose,
                  A/B-test variant assignment, non-tracking site behavior)
   - analytics   (page metrics, behavior, heatmaps)
   - marketing   (ads, retargeting, conversion pixels, social embeds)

   If a vendor is ambiguous, err on the stricter category. GA4 is analytics.
   Meta Pixel is marketing. Hotjar is analytics. Intercom is functional.

3. Produce a complete `window.ZestConfig` object for Zest. Use the LLM-friendly
   markdown reference: https://zest.freshjuice.dev/docs/configuration.md
   Config object must:

   - Sets `mode: "strict"` if marketing trackers are present, otherwise `"safe"`.
   - Adds every offending third-party hostname to `blockedDomains`,
     each as `{ domain, category }`.
   - Adds regex patterns to `patterns.analytics` / `patterns.marketing`
     for cookies or storage keys not covered by Zest's defaults.
   - Keeps `respectDNT: true` and `dntBehavior: "reject"`.
   - Sets `policyUrl` to "/privacy" (I will update it).

4. Consent Mode integrations: Zest has BUILT-IN support for Google Consent
   Mode v2 and Microsoft UET Consent Mode. Do NOT write a gtag stub or an
   onChange callback manually. Zest handles the default-denied state and
   the update signals automatically. Just set the right flags:

   - If the trace shows ANY Google product (googletagmanager.com,
     google-analytics.com, doubleclick.net, googleadservices.com,
     googlesyndication.com), set `consentModeGoogle: true`.
   - If the trace shows Microsoft (bat.bing.com, clarity.ms,
     clarity.microsoft.com, scripts.clarity.ms), set `consentModeMicrosoft: true`.
   - If neither is present, omit both flags (defaults are false).

5. Return ONLY the final HTML, in this exact order:
   - First, a `<script>` block defining `window.ZestConfig = { ... }`.
   - Second, a `<script src="https://cdn.jsdelivr.net/npm/@freshjuice/zest"></script>` loader tag.

   The config block MUST come BEFORE the loader. Zest auto-inits on load
   and reads window.ZestConfig once. Wrong order = config ignored.

   No commentary. No "here is your config". Just the two script tags.

Paste below.
```

Replace `[YOUR DOMAIN]` and `[STACK]`, then paste the report or attach the HAR. Hit enter.

> **Tip for your own prompts:** Zest ships its documentation in three LLM-friendly granularities, pick the smallest one that covers your question:
> 
> -   **Per-page** like [`/docs/configuration.md`](https://zest.freshjuice.dev/docs/configuration.md), [`/docs/events.md`](https://zest.freshjuice.dev/docs/events.md), [`/docs/script-blocking.md`](https://zest.freshjuice.dev/docs/script-blocking.md), [`/docs/api.md`](https://zest.freshjuice.dev/docs/api.md), [`/docs/getting-started.md`](https://zest.freshjuice.dev/docs/getting-started.md), [`/docs/examples.md`](https://zest.freshjuice.dev/docs/examples.md), [`/docs/styling.md`](https://zest.freshjuice.dev/docs/styling.md). About 250 lines each. Use for one-shot questions about a single API. This is what the prompt above uses.
> -   **[`/docs-full.md`](https://zest.freshjuice.dev/docs-full.md)**: every doc page concatenated into a single 2,100-line markdown file. Use for cross-cutting work (custom events + headless mode + script-blocking tuning in one prompt).
> -   **[`/llms.txt`](https://zest.freshjuice.dev/llms.txt)** and **[`/llms-full.txt`](https://zest.freshjuice.dev/llms-full.txt)**: the standard llms.txt index, and the full dump including changelog, homepage, and policy pages. Use for “give the AI everything about this project” prompts.
> 
> Same content as the human-facing docs in every case, no HTML chrome, no wasted tokens.

You will get something like this back. (This is a real output for a HubSpot site I audited last week, lightly edited.)

```
<script>
  window.ZestConfig = {
    mode: "strict",
    respectDNT: true,
    dntBehavior: "reject",

    // Built-in Consent Mode integrations. Zest handles the default-denied
    // state and pushes update signals to gtag/uetq automatically.
    consentModeGoogle: true,
    consentModeMicrosoft: true,

    position: "bottom-left",
    theme: "auto",
    accentColor: "#ff6a00", // Any hex. Zero forced branding. The banner looks like your site, not like Zest.
    policyUrl: "/privacy",

    blockedDomains: [
      { domain: "googletagmanager.com", category: "analytics" },
      { domain: "google-analytics.com", category: "analytics" },
      { domain: "stats.g.doubleclick.net", category: "marketing" },
      { domain: "connect.facebook.net", category: "marketing" },
      { domain: "static.hotjar.com", category: "analytics" },
      { domain: "script.hotjar.com", category: "analytics" },
      { domain: "snap.licdn.com", category: "marketing" },
      { domain: "js.hs-scripts.com", category: "analytics" },
      { domain: "js.hs-banner.com", category: "essential" },
      { domain: "track.hubspot.com", category: "analytics" },
    ],

    patterns: {
      analytics: ["^_ga", "^_gid", "^_hjSession", "^hubspotutk$", "^__hstc$"],
      marketing: ["^_fbp$", "^_gcl_", "^li_sugr$", "^lidc$", "^bcookie$"],
    },
  };
</script>
<script src="https://cdn.jsdelivr.net/npm/@freshjuice/zest"></script>
```

**Order matters.** The `window.ZestConfig` block has to come BEFORE the loader script. Zest auto-initializes the moment it loads, reads `window.ZestConfig` once, and runs with whatever it finds. If the config block is below the loader, Zest boots with defaults and your blocklist never applies.

That is a working, compliant Zest config for a real site. The AI did not invent it. It read the trace, applied Zest’s schema, and produced something you can ship.

### Step 3: Drop it in your `<head>` (1 minute)

Paste both `<script>` tags (the Zest one and your generated config) into the very top of your `<head>`, before any analytics, before any chat widget, before anything.

> **Three deployment options, ranked by privacy footprint:**
> 
> 1.  **jsDelivr CDN** (what the example above uses). One third-party fetch, but jsDelivr has a published GDPR Data Processing Agreement and runs on Cloudflare. Easiest setup, easy to bump versions. Good default.
> 2.  **Self-host as a file** on your origin. Build Zest locally (`npm run build`), drop `zest.en.min.js` somewhere on your server, point `<script src>` at it. Zero third-party origins. You own the asset, you own the cache headers, you own the audit trail.
> 3.  **Inline directly in `<head>`** (zero network requests, zero third-party origins, zero anything). Paste the contents of `zest.en.min.js` between `<script>` tags right above the `ZestConfig` block. Adds about 12KB gzipped to your HTML response, but eliminates one round-trip AND any third-party origin from the pre-consent picture. This is what we run on our own demo site, and what ConsentTheater’s strictest audits expect for sites where every pre-consent request matters.

Order matters. Zest needs to load first so it can intercept `document.cookie`, `localStorage`, and any `<script>` that tries to phone home. If GA loads before Zest, GA wins, and you are back where you started.

For the script-blocking modes (`manual`, `safe`, `strict`, `doomsday`), the [Zest docs](https://zest.freshjuice.dev/docs/script-blocking/) explain what each one does. Short version: `safe` blocks the obvious analytics and marketing trackers from a built-in list. `strict` blocks more aggressively. `doomsday` blocks anything Zest does not recognise, which will break things, so use it only after you have explicitly whitelisted what you need.

### Step 4: Re-scan and verify (2 minutes)

Open your site, click the ConsentTheater icon, and hit **Start test** again. The extension wipes cookies and storage before each run, so you do not need incognito mode or a fresh window.

You are looking for one thing: **zero non-essential cookies and zero non-essential third-party requests before consent is given**.

> **One funny gotcha:** ConsentTheater will probably flag `cdn.jsdelivr.net` as a low-risk third-party connection. Yes, the CDN we just used to load the blocker shows up in the blocker’s own audit. It is essential (Zest itself cannot load without it) and it sets no cookies. If it bothers you, self-host the script and the entry disappears. We told you self-hosting was the cleaner option.

If something still slips through, copy the new report back into Claude with a follow-up prompt:

> The audit still shows `[cookie name or domain]` firing before consent. Update the config so it gets blocked. Return the full updated script blocks in the same order (config first, loader second).

Iterate until the audit is clean. Usually one or two rounds.

### Step 5: Test the “Reject All” path in the same scan (2 minutes)

This is the step everyone forgets. A compliant banner has to actually respect “reject”. The good news: you do not need a separate audit run. ConsentTheater scans live. It clears cookies and storage when the test starts, then watches every script, cookie, and storage write in real time until you stop the scan.

So in a single test session: let the page load (this captures pre-consent leaks), then click **Reject All** in your Zest banner. Anything that fires after that click gets added to the same list. If the post-reject list grows, your banner is leaking on the reject path.

The post-reject list should be empty. Cookies, storage, third-party requests, all of it. If something does show up, copy that name or domain into the AI prompt:

> ConsentTheater shows `[cookie or domain]` firing after the user clicked “Reject All”. Update the config so it is blocked even when consent is denied. Return the full updated script blocks in the same order (config first, loader second).

Re-deploy. Re-run the same scan. Done.

That is the whole loop.

## Why this beats “buy a CMP”

Four reasons, in order of importance:

**1\. You can read the code.**

Zest is on [GitHub](https://github.com/freshjuice-dev/zest?utm_source=freshjuice.dev). The blocklist is in the repo. The categorization logic is in the repo. The UI is in the repo. If something behaves weirdly, you can step through it. With OneTrust or Cookiebot, when something behaves weirdly, you open a support ticket and wait three days.

**2\. Your CMP is not itself a GDPR violation.**

This is the joke nobody in the consent industry wants to tell out loud. Most of the big CMP scripts are loaded from US-hosted CDNs. The moment that script executes on an EU visitor’s page, the visitor’s IP address is in the hands of a US data processor. Before they have consented to anything. That visitor never got the chance to say no, because the thing that was supposed to ask them is the thing that just leaked their data.

Under [Schrems II](https://www.edpb.europa.eu/our-work-tools/our-documents/recommendations/recommendations-012020-measures-supplement-transfer_en?utm_source=freshjuice.dev), a third-country transfer to a US processor is only lawful with Standard Contractual Clauses, a Transfer Impact Assessment, and supplementary measures, because US surveillance law (FISA 702, EO 12333) lets US intelligence compel that processor to hand over data regardless of any contract. Most sites buying these CMPs have done none of that paperwork and would lose an audit immediately.

The compliance tool is the first compliance breach on every page. Zest is open source and, if you self-host the script (we recommended this in [Step 3](#step-3-drop-it-in-your-head-1-minute)), it involves zero third-country transfers. Even when loaded from jsDelivr (a public open-source CDN that hosts every npm package on earth, not our infrastructure), the only data flowing is the HTTP request for the static script file. No cookies. No identifiers. No analytics callback. And jsDelivr runs on Cloudflare with a published DPA, so even that single request has the paperwork in place.

**3\. The AI is auditing your site, not the vendor’s site.**

The big CMPs scan your site with their own scanner, then categorize cookies according to their own rules, which conveniently tend to favor “let it through” over “block it”. An independent scanner (ConsentTheater) plus an independent reasoner (Claude) plus an independent enforcer (Zest) has no incentive to lie to you.

**4\. It costs zero.**

ConsentTheater: **free and open source.** Claude: free tier covers a single site (the only proprietary piece, and they give the model away). Zest: **free and open source**, MIT-licensed, 16KB, every line on [GitHub](https://github.com/freshjuice-dev/zest?utm_source=freshjuice.dev). Two of the three tools you can fork, audit, self-host, or fix yourself. Total annual cost: zero. And the compliance outcome beats most $2,000/year CMPs, because nobody in this stack gets paid to misclassify trackers. The paid CMPs do. We already showed you one selling cookie compliance while dropping non-exempt Google Analytics cookies on its own homepage.

## When AI gets it wrong (and what to do)

The model will occasionally misclassify something. Patterns we have seen:

-   **HubSpot’s `__hs_opt_out` cookie** is genuinely essential (it stores your consent decision). AI sometimes flags it as marketing. Move it to `essential`.
-   **Cloudflare’s `__cf_bm` and `_cfuvid`** are security cookies. Essential. AI sometimes flags them as functional or analytics. They are not.
-   **Theme / locale / contrast / font-size preference set by user action**: AI tends to be over-cautious here and tags these as `functional` (which means Zest blocks them pre-consent and the toggle the user just clicked silently fails to persist). They should be `essential`. If the user clicked a toggle or made a dropdown choice, you are storing what they explicitly asked for, which is exempt from consent under “necessary for the service the user requested”. Only auto-detected / auto-assigned preferences belong in `functional`. And do not let anyone convince you the cookie vs localStorage distinction matters here. ePrivacy Article 5(3) covers both. What matters is whether the user actively chose it. In your Zest config, the cleanest fix is to list the storage keys via `essentialKeys`: `essentialKeys: ['app_theme', 'app_locale', 'a11y_contrast']`.

The fix is always the same: tell the AI which calls were wrong, paste the schema link again, and ask for an updated config. Two or three iterations and you have something solid.

## What `consentModeGoogle` and `consentModeMicrosoft` actually do

Two booleans, two whole CMP integrations. Worth knowing what is happening behind them so you can debug if something looks off in GA4 or Microsoft Advertising.

When `consentModeGoogle: true` is set, Zest:

1.  Initialises `window.dataLayer` and a `gtag()` function if they do not exist yet.
2.  Pushes `gtag('consent', 'default', { ... 'denied' ... wait_for_update: 500 })` before any Google tag has a chance to fire. This is the bit Google has been requiring for EU traffic since March 2024.
3.  On every consent change, pushes `gtag('consent', 'update', { ... })` with the standard category mapping: Zest’s `analytics` → Google’s `analytics_storage`, Zest’s `marketing` → Google’s `ad_storage` + `ad_user_data` + `ad_personalization`.

When `consentModeMicrosoft: true` is set, Zest does the same thing for Microsoft UET via `window.uetq.push('consent', 'default'|'update', { ad_storage: 'granted'|'denied' })`. This matters if you run Bing Ads or Microsoft Clarity, both of which now require their own consent signal.

Two flags, full coverage of the two ad platforms that account for ~98% of paid search globally. No gtag stub to write, no `onChange` callback to maintain, no Tag Manager template to deploy, no $1,200/year CMP “add-on” to license. The [Zest events docs](https://zest.freshjuice.dev/docs/events/) cover the underlying callback signature if you ever do need to wire up Meta CAPI, LinkedIn Conversion API, or a custom server-side endpoint by hand.

To verify Google Consent Mode is working: open GA4 → Admin → Property Settings → Data Streams → your stream → consent settings tab. Google will show you whether Consent Mode signals are arriving and what state they were last in. For Microsoft: UET Tag Helper extension in Edge or Chrome shows live consent signals as they fire.

## The bigger point

You do not need a six-figure compliance stack to ship a legal cookie banner. You need:

-   An honest scanner.
-   A reasoning engine that has read the law.
-   An enforcer that actually blocks things before consent.

ConsentTheater, Claude, Zest. Three free tools. Ten minutes. One `<script>` tag in your head.

The companies selling you complexity are doing it because complexity is profitable for them. It is not profitable for you, and the regulators do not give you extra credit for spending more. They check the cookies. The cookies either fire before consent or they do not.

Make sure yours do not. Then move on to the actual work.

May the 4th be with you,  
[Alex](https://alex.zappa.dev)

-   [#GDPR](https://freshjuice.dev/tags/gdpr/) ,
-   [#Privacy](https://freshjuice.dev/tags/privacy/) ,
-   [#DataPrivacy](https://freshjuice.dev/tags/data-privacy/) ,
-   [#AITools](https://freshjuice.dev/tags/ai-tools/)
