Cookie audit methodology for analytics stacks
last verified · against MDN, Chrome DevTools docs, RFC 6265 & ICO PECR guidance as of 2026-07
Cookie audit methodology for analytics stacks: the ICO audit checklist, cookie and Web Storage fields, DevTools inventory steps, and consent-gate classification.
What this is
A cookie audit is the systematic inventory of every cookie and client-side storage mechanism a site sets, recording each item’s attributes, purpose, lifespan, and party so it can be classified for consent. This reference lists the fields to capture, the DevTools locations that expose them, the ICO checklist an audit must satisfy, and the CMP categories the output maps onto.
Method — two-pass norm
The practical pattern is automation to discover, humans to classify. A
single point-in-time scan of one page misses interaction-gated and
consent-gated cookies, and JavaScript scanners cannot see HttpOnly or
backend-set cookies at all — the ICO explicitly asks for “a combination of
browser-based tools and server-side code review”.
| Pass | Tooling | Catches |
|---|---|---|
| 1. Discover | Automated CMP scanner / crawler (e.g. Cookiebot, OneTrust) | Cookies at scale; drafts the declaration |
| 2. Verify | Manual DevTools walk plus server-side code review | Interaction- and consent-gated cookies; HttpOnly/backend cookies; resolves Unclassified |
| Pre-consent | Fresh load with no interaction, then Reject-All | Non-essential tags firing before opt-in (the regulatory defect) |
Set-Cookie attributes to capture
| Attribute | What it controls / records |
|---|---|
Domain |
Which hosts may receive the cookie |
Path |
URL path that must match for the cookie to be sent |
Expires |
Date/time at which the browser deletes the cookie |
Max-Age |
Seconds until deletion; takes precedence over Expires when both are present |
Secure |
Sent only over HTTPS |
HttpOnly |
Not accessible to JavaScript / document.cookie |
SameSite |
Cross-site send behaviour (see next table) |
SameSite values
SameSite |
Sent when | Notes |
|---|---|---|
Strict |
Same-site / same-origin requests only | — |
Lax |
Top-level navigations to the origin site | Browser default when SameSite is not set |
None |
Cross-site requests | Requires Secure; DevTools flags these with a warning icon in Application and Network panels |
Classification fields the audit must derive
| Field | Rule |
|---|---|
| Session vs persistent | Session = no Max-Age and no Expires (persistent-flag false; removed when the session ends). Persistent = has Max-Age or Expires |
| First vs third party | First party = set by and sent to the visited site. Third party = domain and/or scheme differs from the visited site (set via embedded iframes). Classify by domain, not by which company benefits |
Name prefix __Secure- |
Signals it must be set with Secure from an HTTPS page |
Name prefix __Host- |
Signals it must have Secure, must NOT carry Domain, and must set Path=/ (host-locked) |
DevTools Cookies table — Application > Storage > Cookies
| Column | Reading |
|---|---|
| Name, Value | Cookie identity; Show URL-decoded reveals unencoded values |
| Domain, Path | Scope |
| Expires / Max-Age | Displays the literal Session for session cookies |
| Size | Cookie size in bytes; auto-updates and is not editable |
| HttpOnly, Secure, SameSite | Security / scope flags |
| Partition Key | Top-level site for partitioned cookies |
| Priority | Low, Medium, or High |
Filter by Name or Value (case-insensitive) via the Filter box. Double-click to edit any field except Size, double-click an empty row to add a cookie, delete a selected cookie, or Clear all.
Non-cookie storage surfaces — Application > Storage
| Surface | Lifetime / scope |
|---|---|
| Local Storage | Partitioned by origin; persists after the browser is closed and reopened |
| Session Storage | Partitioned per tab AND origin; destroyed when the tab closes |
| IndexedDB | Structured client-side database |
| Web SQL | Legacy |
| Application Cache | Legacy |
| Extension Storage | Browser-extension storage |
Profile note: in private/incognito mode, localStorage behaves like
sessionStorage — the APIs work but all data is deleted when the private
window or tab closes. Choose the audit profile deliberately.
RFC 6265 storage minimums
These are floors a user agent SHOULD support, not per-browser ceilings.
| Limit | Minimum |
|---|---|
| Per cookie (name + value + attributes) | 4096 bytes |
| Cookies per domain | 50 |
| Cookies total | 3000 |
Real Chrome / Firefox / Safari ceilings differ and are not consistently documented across browsers — do not treat any single number as a universal hard limit.
ICO cookie-audit checklist
| Step | Requirement |
|---|---|
| 1 | Identify every cookie operating on / through the site using browser-based tools AND server-side code review |
| 2 | Confirm the purpose(s) of each cookie |
| 3 | Confirm the type — session or persistent |
| 4 | Distinguish strictly necessary from non-essential |
| 5 | Determine each persistent cookie’s lifespan and whether the duration is justifiable |
| 6 | Determine first or third party, and who sets the third-party ones |
| 7 | Confirm whether cookies are linked to other user information (e.g. usernames) and whether personal data is processed |
| 8 | Identify what data each cookie holds / processes |
| 9 | Ensure the consent mechanism lets users control ALL non-essential cookies, including third-party |
| 10 | Double-check the privacy information is accurate for each cookie |
| 11 | Confirm what is shared with third parties and what users are told |
| 12 | Document findings and follow-up actions |
Scope note: PECR regulation 6 covers “any similar technologies” beyond cookies — Local Shared Objects (Flash cookies), pixels, device fingerprinting, and any method of storing or accessing information on a device — so extend steps 1 to 12 to the non-cookie surfaces above.
Strictly-necessary exemption (ICO)
The exemption is narrow: a cookie must be essential to a service the user actually requested.
| Consent needed? | Examples |
|---|---|
| Exempt | Online shopping-basket cookie; security session cookie essential to a requested service (e.g. online banking); load-balancing cookie |
| Consent required | Analytics services; online advertising; cookies that are merely helpful or convenient; cookies essential only for the operator’s own purposes |
The ICO states the legitimate purpose of facilitating a requested service “does not include third parties such as analytics services or online advertising” — so analytics and advertising cookies must be gated behind consent.
Consent standard for the classification output (ICO)
| Rule | Requirement |
|---|---|
| Positive action | Consent must be freely given, specific and informed, given by a clear affirmative act (ticking / clicking) — more than continuing to use the site |
| No pre-enabling | Non-essential cookies must not be pre-ticked and must not be set before consent, homepage included |
| No bundling | Consent cannot be buried in terms and conditions or a privacy notice |
| Disclosure | Give clear, comprehensive information on the purpose AND duration of each cookie |
| Scope of control | The mechanism must let users control all cookies the site sets, including third-party |
CMP category map (Cookiebot / Usercentrics)
The dominant model is a fixed set of four categories plus an Unclassified bucket. You can rename categories but cannot add or truly delete one, and consent is granted per entire category.
| Category | Definition / example |
|---|---|
| Necessary | Scripts needed to guarantee website functionality (e.g. shopping-basket cookie) |
| Preferences | Remembering user choices to navigate the site |
| Statistics | Analytical purposes (e.g. Google Analytics cookies) |
| Marketing | Targeting users with (personalized) ads (e.g. Facebook pixel) |
| Unclassified | Category unknown; “may be necessary”; needs manual classification before publishing a declaration |
This four-category scheme is a vendor convention, not a legal taxonomy — the IAB TCF uses a different purpose scheme.
Re-audit cadence (regulatory guidance)
The ICO does not set a fixed interval. It requires that the audit “build in an appropriate review period” and that teams “undertake regular reviews of your cookie usage, as well as any third party services your website includes that may set cookies”; fresh consent may be needed if cookie use changes. Any specific cadence (quarterly, monthly, twice-yearly) seen in vendor material is industry recommendation, not a regulatory requirement.
Gotchas
- Session cookie never clears between runs — browser session-restore keeps
session cookies alive across restarts, so “until the session ends” can
mean indefinitely. Fix: audit in a fresh/incognito profile, disable
“continue where you left off”, and use
Clear-Site-Dataor DevTools “Clear all” between passes rather than relying on browser close. - A third-party cookie shows a first-party
Domainand slips the consent gate — code injected via a<script>tag runs in the embedder’s context and writes to first-party storage; theDomaincolumn reflects where it was written, not who controls it. Fix: classify by who sets/controls the cookie and by the network domain of the requests that create it (Network panel,Set-Cookieheaders), cross-referenced against the server-side vendor list. - The published inventory is incomplete — the auditor inspected only the
Cookies pane on the homepage; non-cookie storage and cookies that fire
after interaction, on deeper pages, or after Accept were never captured.
Fix: inspect every
Application > Storagetype and drive a runtime walk that navigates key journeys and clicks Accept / Reject / Customize. - A scanner report is full of “Unclassified” and the declaration is challenged — the scanner’s database did not recognise the cookie or its purpose could not be determined. Fix: treat every Unclassified entry as a manual work item — research purpose and lifespan, confirm the setting party, assign a category by hand, and do not leave it ungated.
- A cookie expected on a cross-site request is silently not sent — with no
SameSiteattribute the browser defaults it toLaxand withholds it on cross-site subrequests; onlySameSite=None; Secureis sent cross-site. Fix: recordSameSitefor every cookie and flag cross-site-dependent cookies lackingSameSite=None(DevTools surfaces them with warning icons). - Non-essential cookies are set on the first page load before any consent choice — tags fire on page load instead of being gated on consent state; the ICO bars setting non-essential cookies before consent, homepage included. Fix: add a pre-consent pass (load with no interaction, and again with Reject-All) and treat any analytics/marketing cookie present as a defect to fix in the tag/CMP blocking configuration.
Quick recipes
List cookies the current origin exposes to JavaScript:
// document.cookie exposes only non-HttpOnly cookies for the current origin.// HttpOnly and backend-set cookies need the DevTools Application panel// or a server-side code review — the ICO asks for both.document.cookie.split('; ').forEach((c) => console.log(c));Dump both Web Storage areas (a separate audit surface from cookies):
for (const [label, store] of [['localStorage', localStorage], ['sessionStorage', sessionStorage]]) { console.group(label); for (let i = 0; i < store.length; i++) { const key = store.key(i); console.log(key, '=', store.getItem(key)); } console.groupEnd();}Enumerate IndexedDB databases (part of Application > Storage):
// Run in the DevTools console (top-level await is allowed there).console.log(await indexedDB.databases());Pre-consent test pass — anything printed here is a compliance finding:
// Load the page fresh with NO consent interaction; repeat after Reject-All.// ICO: no non-essential cookie may be set before consent, homepage included.// Print everything present, then classify each cookie by hand.console.table( document.cookie.split('; ').filter(Boolean).map((c) => { const [name, ...rest] = c.split('='); return { name, value: rest.join('=') }; }),);// Repeat against Application > Storage (Local/Session Storage, IndexedDB).Reset state between pre- and post-consent passes:
# Delete one cookie: re-send it expired. Max-Age wins over Expires when both set.Set-Cookie: COOKIE_NAME=; Max-Age=0; Path=/
# Or clear every cookie for the registrable domain and its subdomains at once:Clear-Site-Data: "cookies"Related
- Hub: Privacy and consent guides
- Consent Mode v2 reference — the consent signals the audit’s classification feeds
- Chrome DevTools for analytics debugging — the panels this audit reads
Sources
- Using HTTP cookies — MDN (read 2026-07-20)
- View, edit, and delete cookies — Chrome DevTools docs (read 2026-07-20)
- Web Storage API — MDN (read 2026-07-20)
- RFC 6265: HTTP State Management Mechanism (read 2026-07-20)
- Cookies and similar technologies (in brief) — ICO (read 2026-07-20)
- How do we comply with the cookie rules? — ICO (read 2026-07-20)
- Customizing the cookie categories — Cookiebot Support (read 2026-07-20)
Verification notes: the ICO detailed-guidance page carries a banner that it is based on the previous version and a revised version is under consultation, so treat any post-consultation specifics as unverified. The ICO sets no fixed re-audit interval. RFC 6265 gives only minimum storage capabilities, not real per-browser limits. The Cookiebot/Usercentrics four-category model is a vendor convention, not a legal taxonomy; IAB TCF purposes are not mapped here. This is an implementation reference, not legal advice.
Changelog
- — Initial version, verified against official documentation.