Our home-made test page gave Chrome the same ID, e08e953c, on 5 of 5 visits, and every visit started with a brand new empty profile and zero cookies. Brave, with its default settings, got 5 different IDs in 5 visits on the same Mac.
So clearing cookies changes nothing about what this page sees. We ran this ourselves; below is the method, the reason the two browsers differ, and the limits.
How we measured it
The page reads 7 things from the browser, joins them, and hashes them with SHA-256. The first 8 hex characters are the ID. Two bytes of the ID pick a two-word nickname from two lists of 32 words, so it is easy to read on screen. Chrome was "Bold Llama".
// inside an async function; c is a <canvas> we drew shapes, text and an emoji on
const hex = async s => [...new Uint8Array(await crypto.subtle.digest('SHA-256',
new TextEncoder().encode(s)))].map(b => b.toString(16).padStart(2, '0')).join('');
const canvas = await hex(c.toDataURL());
const ac = new OfflineAudioContext(1, 44100, 44100);
const o = ac.createOscillator(), comp = ac.createDynamicsCompressor();
o.type = 'triangle'; o.frequency.value = 10000;
o.connect(comp); comp.connect(ac.destination); o.start(0);
const buf = (await ac.startRendering()).getChannelData(0);
let sum = 0; for (let i = 4500; i < 5000; i++) sum += Math.abs(buf[i]);
const gl = document.createElement('canvas').getContext('webgl');
const e = gl.getExtension('WEBGL_debug_renderer_info');
const webgl = e ? gl.getParameter(e.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);
const parts = {
canvas, audio: String(sum),
screen: `${screen.width}x${screen.height}x${screen.colorDepth}@${devicePixelRatio}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
languages: navigator.languages.join(','),
cpu_cores: String(navigator.hardwareConcurrency),
webgl
};
const id = (await hex(JSON.stringify(parts))).slice(0, 8);
A small Playwright script (run.js) opens the page with chromium.launchPersistentContext(profile, { executablePath, headless: true }). The profile is a fresh fs.mkdtempSync folder on every visit, deleted afterwards.
python3 -m http.server 60309 --bind 127.0.0.1
node run.js chrome http://127.0.0.1:60309/fp.html 6 # 1 warm-up + 5 measured
node run.js brave http://127.0.0.1:60309/fp.html 6
KEEP=1 node run.js brave http://127.0.0.1:60309/fp.html 6 # one kept profile, restart + one reload per visit
Machine: Apple M1 Pro, 16 GB, macOS 26.6.2. Chrome 155.0.8059.40, Brave 155.1.97.56, Playwright 1.64.0, Node v26.8.1. Both browsers ran headless.
The 60-second version:
The numbers
| Run | Cookies and saved data per visit | Different IDs in 5 visits | Names |
|---|---|---|---|
| Chrome, new empty profile each visit | 0 | 1 | Bold Llama, 5 times |
| Brave, new empty profile each visit | 0 | 5 | Zippy Llama, Mighty Mole, Zippy Wombat, Chilly Ferret, Fancy Lobster |
| Brave, same profile, browser restarted each visit | 0 | 5 | Mellow Panda, Fuzzy Owl, Jolly Owl, Shiny Yak, Cranky Pigeon |
In Chrome all 7 parts had one value across the 5 visits. In Brave only 3 parts moved: the canvas hash (5 different values), the audio sum (5), and the CPU core count, which came back as 7, 4, 8, 7, 7. The other 4 stayed the same.
We also opened brave://settings/shields in a new empty profile. "Block fingerprinting" was already on.
Why the two browsers differ
None of the 7 parts is stored in the profile. They describe the hardware and the system, so a new profile reads them the same way. A cookie is something the site puts on your disk, a fingerprint is something the site asks your machine. Mozilla's Tom Ritter wrote in the Firefox fingerprinting post (10 Nov 2025) that the technique "lets websites identify you even when cookies are blocked or you're in private browsing".
Brave does not block the reads. Per the Brave fingerprinting wiki, it adds small random changes to some API values, from "a seed that changes per session, per site (eTLD+1) and per storage area". A hash over many parts breaks as soon as one part moves, and here three of them moved.
There is also a consent angle. The UK regulator ICO noted on 19 Dec 2024 that from 16 February 2025 Google no longer prohibits fingerprinting in its advertising products, and that Google itself had earlier said users "cannot easily consent to it as they would cookies".
Limits you should know
- One machine, one test, 5 measured visits per run.
- Both browsers ran headless through Playwright with default launch flags. Chrome reported the 1080x1920 viewport as its screen and Brave reported 3840x2160, neither being the real display.
- The page was served from 127.0.0.1. It is a 7-part toy, not an ad tracker. Real trackers can use more signals, including your IP address, which Brave does not hide.
- We showed the Chrome ID is stable. We did not measure how unique it is: two identical Macs could get the same one.
- In Brave the ID changed on every browser start. A reload inside one session kept the same ID, 5 of 5 times, which matches the per-session seed. It is not a new name on every page load.
- Firefox with Strict protection was not tested.
- Our first full run was thrown away: the nickname list had the word "Brave" in it, so Chrome was named "Brave Llama". We changed it to "Bold" and ran everything again; the pattern was the same.
Next we want to run the page in a normal visible window, and on a second identical Mac to see if the IDs collide. If you run it, which of the 7 parts changes on your setup?
Top comments (6)
the brave part is the interesting one. everyone assumes anti-fingerprinting means blocking the reads, but brave doesn’t block anything. it just lies a little differently every session, so the id changes while nothing breaks. perturbing instead of blocking is the whole trick.
honest question though: all 5 visits were on one machine, right? the thing that decides whether this is a demo or an identifier is two identical macs producing the same hash.
Yes, all 5 visits ran on one Mac, an M1 Pro, so you are right: this shows the ID survives a fresh profile, not that it picks out one person. Two identical Macs giving the same hash is the test we did not run, and it is the next one I would like to do.
confirmed, thanks. so chrome's id survives cookie-free resets on the same machine, while brave randomises per visit. opposite strategies, both worth knowing.
The stability half of your result is the part most people get backwards. A real device should return the same ID across visits — that's what real hardware does. Brave changing it per session protects you against cross-session linking, but per-session randomisation has its own tell: no physical laptop produces a brand-new canvas/GPU combination every time it wakes up, and risk models know it.
What I'd love to see in a follow-up run: keep Chrome's stable ID, but check whether the 7 parts stay mutually consistent (does the reported GPU match the canvas output, does the platform match the font set?). In our work on multi-profile tooling (I build NullBrowser, a profile-isolation app, so full disclosure) the failures we see are almost never “the fingerprint was stable” — they're “two signals contradicted each other.” Stability is normal. Contradiction is the costume.
This matches what we saw on our side: clearing cookies is basically a no-op against fingerprinting, because the signals being hashed (canvas rendering, GPU strings, font lists) live outside anything storage-clearing touches - Chrome giving you the same e08e953c on five fresh profiles is expected once hardware acceleration settings match. The Brave asymmetry is deliberate farbling: it trades a little breakage for unlinkability, which is the right default for a privacy browser but makes debugging CDN bot-walls genuinely weird. Did you check whether Brave's IDs re-stabilize within a single site? In my runs the randomization is per-session and per-eTLD, so cross-site linkage fails while same-site persistence still holds.
We only checked half of that. Inside one Brave session a reload kept the same ID 5 of 5 times, and each new browser start gave a new one, which fits the per-session seed. We never loaded the test page from a second site, so the per-eTLD part is still untested on our side.