cdpfleet Docs GitHub Dashboard

Cases / #66 · 2026-10-22 · Medium

Which fingerprints stay the same across launches: canvas, WebGL, audio and fonts on seven browsers

Three launches per browser, each reading a canvas hash, the WebGL renderer and a rendered WebGL image, an audio hash and a font list on the same page. Chrome, Edge, Chromium, Firefox and WebKit gave the same values every time. Brave changed its canvas and audio on every launch, and Camoufox its canvas (and usually its GPU). A profile kept Camoufox's GPU and fonts but not the canvas, and kept nothing of Brave's noise.

Google Chrome Microsoft Edge Chromium Brave Camoufox Firefox WebKit

Run on production on 2026-10-22: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

The problem

Fingerprinting scripts don't only ask "is this a bot?". They also store a hash of your canvas, GPU, audio stack and fonts and compare it next time. A value that never changes ties all your sessions together; a value that changes on every visit can look like a different device each time, which is just as unusual for an account that should be one person. So which of these values does each browser keep from one launch to the next, which does it randomise, and what does a persistent profile change?

What we used, and why

WhatWhy
A small fingerprinting scriptThe same script in every language: a canvas drawing, the WebGL renderer string plus a hash of a rendered WebGL image, an OfflineAudioContext render, and a width probe over 30 font families. Canvas and audio are drawn twice to see whether a session agrees with itself.
https://example.com/The script runs on a real page. Brave and Camoufox key their noise to the site, so on about:blank both looked perfectly stable while building this case.
Three launches per browserEach a new session, headless, through the same proxy. Nothing else is set, so these are the defaults.
"profile": "…"Two more launches per browser with one new profile, to see what the profile carries over. See Persistent profiles.

How it works

  1. For each of seven browsers, launch three sessions, open example.com and run the fingerprinting script.
  2. Launch twice more with one new profile, waiting for the first session's profile to be saved, and run the script again.
  3. Delete the profile and print every hash.

The code

The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:

// npm install [email protected]
// env: CDPFLEET_API_KEY, PROXY_URL
import { randomBytes } from 'node:crypto';
import { chromium, firefox, webkit } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;
const RUN = randomBytes(3).toString('hex');
const ENGINES = [
  ['chrome', chromium], ['edge', chromium], ['chromium', chromium], ['brave', chromium],
  ['camoufox', firefox], ['firefox', firefox], ['webkit', webkit],
];
const LAUNCHES = 3;

// What a fingerprinting script computes: canvas, WebGL (renderer + a rendered image), audio and a
// font list, each as a short hash; canvas and audio are drawn twice to see whether a session is
// consistent with itself. The same script runs in every language.
const FP = `(async () => {
  const fnv = (s) => { let h = 0x811c9dc5; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193) >>> 0; } return h.toString(16).padStart(8, '0'); };
  const draw = () => {
    const c = document.createElement('canvas'); c.width = 240; c.height = 60;
    const x = c.getContext('2d'); x.textBaseline = 'top'; x.font = '16px Arial';
    x.fillStyle = '#f60'; x.fillRect(100, 1, 62, 20);
    x.fillStyle = '#069'; x.fillText('cdpfleet fp 66 <@>', 2, 15);
    x.fillStyle = 'rgba(102, 204, 0, 0.7)'; x.fillText('cdpfleet fp 66 <@>', 4, 17);
    return c.toDataURL();
  };
  const webgl = () => {
    try {
      const c = document.createElement('canvas'); c.width = 64; c.height = 64;
      const gl = c.getContext('webgl', { preserveDrawingBuffer: true });
      if (!gl) return { renderer: null, image: null };
      const dbg = gl.getExtension('WEBGL_debug_renderer_info');
      const renderer = dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);
      const sh = (type, src) => { const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s; };
      const pr = gl.createProgram();
      gl.attachShader(pr, sh(gl.VERTEX_SHADER, 'attribute vec2 p;varying vec2 v;void main(){v=p;gl_Position=vec4(p,0.0,1.0);}'));
      gl.attachShader(pr, sh(gl.FRAGMENT_SHADER, 'precision mediump float;varying vec2 v;void main(){gl_FragColor=vec4(sin(v.x*7.1)*0.5+0.5,cos(v.y*5.3)*0.5+0.5,fract(v.x*v.y*13.7),1.0);}'));
      gl.linkProgram(pr); gl.useProgram(pr);
      gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, 0, 1]), gl.STATIC_DRAW);
      const loc = gl.getAttribLocation(pr, 'p'); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
      gl.drawArrays(gl.TRIANGLES, 0, 3);
      const px = new Uint8Array(64 * 64 * 4); gl.readPixels(0, 0, 64, 64, gl.RGBA, gl.UNSIGNED_BYTE, px);
      return { renderer, image: fnv(px.join(',')) };
    } catch (e) { return { renderer: null, image: null }; }
  };
  const audio = async () => {
    try {
      const ctx = new OfflineAudioContext(1, 44100, 44100);
      const o = ctx.createOscillator(); o.type = 'triangle'; o.frequency.value = 10000;
      const k = ctx.createDynamicsCompressor();
      k.threshold.value = -50; k.knee.value = 40; k.ratio.value = 12; k.attack.value = 0; k.release.value = 0.25;
      o.connect(k); k.connect(ctx.destination); o.start(0);
      // copyFromChannel into our own array: Camoufox runs this script in an isolated world, where
      // reading the page's audio buffer directly is refused.
      const d = new Float32Array(500); (await ctx.startRendering()).copyFromChannel(d, 0, 4500);
      let s = ''; for (let i = 0; i < 500; i++) s += d[i].toFixed(10) + ',';
      return fnv(s);
    } catch (e) { return 'error: ' + e.name; }
  };
  // Width probe: a family counts as installed when it measures differently from all three fallbacks.
  const FAMILIES = ['Arial', 'Arial Black', 'Calibri', 'Cambria', 'Candara', 'Comic Sans MS', 'Consolas', 'Courier New', 'DejaVu Sans', 'Georgia', 'Helvetica', 'Helvetica Neue', 'Impact', 'Liberation Sans', 'Lucida Console', 'Lucida Grande', 'Menlo', 'Monaco', 'Noto Sans', 'Palatino', 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Ubuntu', 'Verdana', 'Roboto', 'Open Sans', 'Fira Code', 'Avenir'];
  const fonts = () => {
    const c = document.createElement('canvas').getContext('2d');
    const w = (f) => { c.font = '48px ' + f; return c.measureText('mmmmmmmmmmlli1WQ@#').width; };
    const base = ['monospace', 'serif', 'sans-serif'].map((b) => [b, w(b)]);
    return FAMILIES.filter((f) => base.some(([b, bw]) => w(JSON.stringify(f) + ', ' + b) !== bw));
  };
  const c1 = draw(), c2 = draw();
  const a1 = await audio(), a2 = await audio();
  const g = webgl();
  const found = fonts();
  return {
    canvas: fnv(c1), canvas_same_in_session: c1 === c2,
    webgl_renderer: g.renderer, webgl_image: g.image,
    audio: a1, audio_same_in_session: a1 === a2,
    fonts: fnv(found.join(',')), font_count: found.length,
    user_agent: navigator.userAgent,
  };
})()`;

async function api(method, path) {
  const res = await fetch(`https://cdpfleet.com${path}`, { method, headers: { 'x-api-key': KEY } });
  return { status: res.status, body: await res.json().catch(() => ({})) };
}
// The node saves a profile after the browser exits: wait for the new version before relaunching.
async function waitForVersion(name, version) {
  for (let i = 0; i < 60; i++) {
    const p = (await api('GET', '/v1/me/profiles')).body.profiles?.find((x) => x.name === name);
    if (p?.version >= version && !p.in_use) return;
    await new Promise((r) => setTimeout(r, 2000));
  }
  throw new Error(`profile ${name} never reached version ${version}`);
}
// 503 = the fleet is momentarily busy: wait Retry-After and try again. 409 profile_version_downgrade =
// the profile was saved by a newer build than this server has (mid-rollout): another try lands elsewhere.
async function launch(engine, body) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`https://starter.cdpfleet.com/${engine}/session`, {
      method: 'POST', headers: { 'x-api-key': KEY, 'content-type': 'application/json' }, body: JSON.stringify(body),
    });
    if (res.ok) return res.json();
    const text = await res.text();
    const retry = res.status === 503 || (res.status === 409 && text.includes('profile_version_downgrade'));
    if (!retry || attempt === 5) throw new Error(`${engine} launch ${res.status} ${text}`);
    await new Promise((r) => setTimeout(r, (Number(res.headers.get('retry-after')) || 5) * 1000));
  }
}
// One session: read the fingerprint on a real page. Brave and Camoufox key their noise to the site,
// so a blank page would hide it.
const SITE = 'https://example.com/';
// Proxies drop a tunnel now and then: up to two retries.
async function go(page) {
  for (let attempt = 1; ; attempt++) {
    try { return await page.goto(SITE, { timeout: 60000 }); } catch (err) {
      if (attempt === 3) throw err;
      await new Promise((r) => setTimeout(r, 2000));
    }
  }
}
async function measure(engine, type, profile) {
  const s = await launch(engine, { proxy: process.env.PROXY_URL, headless: true, record: false, ...(profile ? { profile } : {}) });
  const browser = await type.connect(s.wsUrl, { headers: { 'x-api-key': KEY } });
  try {
    // A profile lives in browser.contexts()[0]; without one, a fresh context.
    const context = profile ? browser.contexts()[0] : await browser.newContext();
    const page = context.pages()[0] ?? await context.newPage();
    await go(page);
    return await page.evaluate(FP);
  } finally {
    await browser.close();
  }
}

const out = { launches: {}, profile: {} };
for (const [engine, type] of ENGINES) {
  out.launches[engine] = [];
  for (let i = 0; i < LAUNCHES; i++) out.launches[engine].push(await measure(engine, type));
  // Two launches with one new profile: what does it keep?
  const name = `case66-${engine}-${RUN}`;
  try {
    const first = await measure(engine, type, name);
    await waitForVersion(name, 1);
    out.profile[engine] = [first, await measure(engine, type, name)];
    await waitForVersion(name, 2);
  } finally {
    await api('DELETE', `/v1/me/profiles/${name}`);
  }
}
console.log(JSON.stringify(out, null, 2));

What we got

BrowserCanvas, 3 launchesWebGL renderer · imageAudioFonts found (of 30)With a profile: canvas · renderer
Google Chromesamesame · samesame6 (same)kept · kept
Microsoft Edgesamesame · samesame6 (same)kept · kept
Chromiumsamesame · samesame6 (same)kept · kept
Brave3 differentsame · same3 different1 (same)new · kept
Camoufox3 different3 different · samesame17 / 3 (2 different)new · kept
Firefoxsamesame · samesame5 (same)kept · kept
WebKitsamesame · samesame6 (same)kept · kept

From the Node.js run on 2026-10-22. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.

Raw output (Node.js)
{
  "launches": {
    "chrome": [
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      }
    ],
    "edge": [
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36 Edg/155.0.0.0"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36 Edg/155.0.0.0"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36 Edg/155.0.0.0"
      }
    ],
    "chromium": [
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "d432c413",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "d432c413",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "d432c413",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36"
      }
    ],
    "brave": [
      {
        "canvas": "75d44b12",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "d54a031b",
        "audio_same_in_session": true,
        "fonts": "4887d8f3",
        "font_count": 1,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      },
      {
        "canvas": "2bac1cf6",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "02ad4abc",
        "audio_same_in_session": true,
        "fonts": "4887d8f3",
        "font_count": 1,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      },
      {
        "canvas": "22ccb850",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "9dc0da77",
        "audio_same_in_session": true,
        "fonts": "4887d8f3",
        "font_count": 1,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      }
    ],
    "camoufox": [
      {
        "canvas": "f704ceae",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "2a2dc77e",
        "font_count": 17,
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:156.0) Gecko/20100101 Firefox/156.0"
      },
      {
        "canvas": "5a2edf68",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Intel, Intel(R) HD Graphics 400 Direct3D11 vs_5_0 ps_5_0), or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "2a2dc77e",
        "font_count": 17,
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:156.0) Gecko/20100101 Firefox/156.0"
      },
      {
        "canvas": "446f6d94",
        "canvas_same_in_session": true,
        "webgl_renderer": "llvmpipe, or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "57c9fd7c",
        "font_count": 3,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:156.0) Gecko/20100101 Firefox/156.0"
      }
    ],
    "firefox": [
      {
        "canvas": "2bbe8a63",
        "canvas_same_in_session": true,
        "webgl_renderer": "llvmpipe, or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "9cd21bcd",
        "font_count": 5,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"
      },
      {
        "canvas": "2bbe8a63",
        "canvas_same_in_session": true,
        "webgl_renderer": "llvmpipe, or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "9cd21bcd",
        "font_count": 5,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"
      },
      {
        "canvas": "2bbe8a63",
        "canvas_same_in_session": true,
        "webgl_renderer": "llvmpipe, or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "9cd21bcd",
        "font_count": 5,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"
      }
    ],
    "webkit": [
      {
        "canvas": "a6d70d50",
        "canvas_same_in_session": true,
        "webgl_renderer": "Apple GPU",
        "webgl_image": "311ab43e",
        "audio": "07b0aac9",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.4 Safari/605.1.15"
      },
      {
        "canvas": "a6d70d50",
        "canvas_same_in_session": true,
        "webgl_renderer": "Apple GPU",
        "webgl_image": "311ab43e",
        "audio": "07b0aac9",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.4 Safari/605.1.15"
      },
      {
        "canvas": "a6d70d50",
        "canvas_same_in_session": true,
        "webgl_renderer": "Apple GPU",
        "webgl_image": "311ab43e",
        "audio": "07b0aac9",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.4 Safari/605.1.15"
      }
    ]
  },
  "profile": {
    "chrome": [
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      }
    ],
    "edge": [
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36 Edg/155.0.0.0"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "b7a0343c",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36 Edg/155.0.0.0"
      }
    ],
    "chromium": [
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "d432c413",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36"
      },
      {
        "canvas": "1f06343f",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "d432c413",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36"
      }
    ],
    "brave": [
      {
        "canvas": "653fa36e",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "a188ff79",
        "audio_same_in_session": true,
        "fonts": "4887d8f3",
        "font_count": 1,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      },
      {
        "canvas": "a63d5c5e",
        "canvas_same_in_session": true,
        "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
        "webgl_image": "0eba6e27",
        "audio": "a89dabd9",
        "audio_same_in_session": true,
        "fonts": "4887d8f3",
        "font_count": 1,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36"
      }
    ],
    "camoufox": [
      {
        "canvas": "d145e8ec",
        "canvas_same_in_session": true,
        "webgl_renderer": "Apple M1, or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "6160a704",
        "font_count": 17,
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:156.0) Gecko/20100101 Firefox/156.0"
      },
      {
        "canvas": "1033ef85",
        "canvas_same_in_session": true,
        "webgl_renderer": "Apple M1, or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "6160a704",
        "font_count": 17,
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:156.0) Gecko/20100101 Firefox/156.0"
      }
    ],
    "firefox": [
      {
        "canvas": "2bbe8a63",
        "canvas_same_in_session": true,
        "webgl_renderer": "llvmpipe, or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "9cd21bcd",
        "font_count": 5,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"
      },
      {
        "canvas": "2bbe8a63",
        "canvas_same_in_session": true,
        "webgl_renderer": "llvmpipe, or similar",
        "webgl_image": "2818fc2e",
        "audio": "6eb5748e",
        "audio_same_in_session": true,
        "fonts": "9cd21bcd",
        "font_count": 5,
        "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"
      }
    ],
    "webkit": [
      {
        "canvas": "a6d70d50",
        "canvas_same_in_session": true,
        "webgl_renderer": "Apple GPU",
        "webgl_image": "311ab43e",
        "audio": "07b0aac9",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.4 Safari/605.1.15"
      },
      {
        "canvas": "a6d70d50",
        "canvas_same_in_session": true,
        "webgl_renderer": "Apple GPU",
        "webgl_image": "311ab43e",
        "audio": "07b0aac9",
        "audio_same_in_session": true,
        "fonts": "069b1d9e",
        "font_count": 6,
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.4 Safari/605.1.15"
      }
    ]
  }
}

Takeaways