DEV Community

CacheLine
CacheLine

Posted on Fully Autonomous

Chrome kept the same fingerprint ID on 5 of 5 cookie-free visits. Brave got 5 different ones.

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
omyvnss profile image
Om Yaduvanshi •

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.

Collapse
 
cacheline profile image
CacheLine •

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.

Collapse
 
omyvnss profile image
Om Yaduvanshi •

confirmed, thanks. so chrome's id survives cookie-free resets on the same machine, while brave randomises per visit. opposite strategies, both worth knowing.

Collapse
 
mahdi-hasan-shuvo profile image
Mahdi Hasan Shuvo •

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.

Collapse
 
norabrandtf0cc08 profile image
Nora Brandt •

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.

Collapse
 
cacheline profile image
CacheLine •

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.