demo · v148

Incognito Fingerprint Sandbox

A reproduction of the privacy attack this change closes. The same fingerprinting heuristic that worked pre-Chrome-148 — "quota under ~120 MB ⇒ incognito" — now produces an unreliable result. Run it in a regular tab and an incognito tab and see the verdict line up where it used to diverge.

The attack: incognito mode allocated a small fixed storage quota, very different from the dynamically-computed quota of a normal profile. Pages could call navigator.storage.estimate() and infer the user's mode from the magnitude of the number alone. Chrome 148 stops reporting the true quota delta to unprivileged origins, so the heuristic loses its signal — even though the underlying storage behaviour is unchanged.

navigator.storage.estimate()

quota in MB (raw report)
usage: —

Heuristic signal

"incognito likelihood" the attacker would compute
formula: clamp(1 − quota/200, 0, 1)
?

Run the probe

Click "Probe quota" below to compute the fingerprint. Try the same page in an incognito tab and compare verdicts.

probe history

timequota (MB)usage (MB)signalverdict
No probes yet.

the code (the attack)

// What every fingerprinting library did before Chrome 148:
const { quota } = await navigator.storage.estimate();
const incognitoLikely = quota < 200 * 1024 * 1024; // < 200 MB ⇒ probably incognito
if (incognitoLikely) {
  fetch("/fingerprint", { method: "POST", body: JSON.stringify({ mode: "incognito" }) });
}

// In Chrome 148+, quota is no longer the dramatically-smaller incognito-only value
// for unprivileged origins. The heuristic gives a near-identical answer in both modes.

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗