Skip to content
Fingerprinting

Storage quota and private mode: what navigator.storage.estimate() still reveals

The storage quota used to reveal disk size and incognito mode. Chrome now reports a fixed number, so detection moved to how long it takes to write one byte.

Pim
Pim· Clearcote Research
7 min read

Key takeaways

  • Chrome 156 reports a quota of usage plus 10 GiB to every site in every mode; with that behaviour switched off, our test machine reported 60 % of its disk (575,125,787,442 bytes on a 958 GB volume) normally and about 5 % of RAM in an incognito context.
  • A deployed challenge widget reads the quota and also times a one-byte write and flush() in the origin private file system: 0 to 0.1 ms when storage lives in memory, 1 to 16 ms on our machine when it is a real disk write.
  • Safari's numbers are formulas too: a quota of exactly 104,857,600 bytes is WebKit's 1000 MiB default times the one-tenth share of a third-party frame, which appears when there is no volume to measure, as in a private session.

What the quota used to say

navigator.storage.estimate() resolves to the origin's current usage and its quota, the amount it may store. Browsers compute the quota from the device: Chromium gives each site 60 % of the total size of the disk that holds the profile, Firefox 10 % up to 10 GiB, and Safari 60 % of the volume in browsers and 15 % in other apps, with one tenth of that for a cross-origin frame.

That made the quota a fingerprint of the disk size, and a reliable incognito test: Chromium's incognito storage lives in memory and its quota is derived from RAM, in current code physical memory times a random factor between 0.15 and 0.20, divided by three. A quota far below the disk-based value meant a private window, and incognito-detection scripts relied on exactly that for years.

Chrome's fixed reported quota

Chrome now separates the quota it enforces from the quota it reports. Sites without unlimited-storage permission are told their usage plus the smaller of 10 GiB and the disk size, in every browsing mode; the enforced limit is unchanged. Sources disagree on the first milestone that shipped it by default, but it is on in Chrome 156. We measured on a Linux machine:

  • Normal profile, either of two disks: exactly 10,737,418,240 bytes (10 GiB).
  • After writing 200 MiB: usage plus 10 GiB.
  • Incognito context: also 10 GiB.
  • With the fixed quota disabled: 575,125,787,442 bytes on a 958 GB volume and 399,486,637,670 on a 666 GB volume, which is 60 % of the total size even though the second disk had only 8.5 GB free; and 1,727,115,475 bytes in an incognito context, 0.154 times the RAM divided by three.

So in current Chrome the quota no longer reveals the disk or the mode. A Chrome 156 session that reports a disk-based quota is running an older or differently configured build.

The new tell: one flush

The same challenge widget that reads the quota also starts a worker that creates a file in the origin private file system, writes one byte through a synchronous access handle, and times flush(). In a normal profile, flushing is a real write to disk with a sync. In incognito, the file system lives in memory and flushing does nothing.

Measured in Chrome 156 with twelve flushes per run: 1 to 16 ms in a normal profile on our machine, and 0 to 0.1 ms in an incognito context, every time. The widget's result for one session is either the flush time or the error message if the file system is unavailable.

The same idea has a public history. An open-source incognito-detection library replaced its quota test with OPFS flush timing in May 2026, then replaced that in July 2026 because 8 of 18 real Android phones flushed to fast flash storage in under 0.1 ms in normal mode. On desktop the separation is clean; on phones an absolute threshold is not.

Safari and Firefox

Safari reports a device-class constant rather than the real disk size, derived from a standard volume capacity of 64 GB on iOS and 128 GB on macOS, until usage grows. When it cannot measure a volume at all, it falls back to a default per-origin quota of 1000 MiB, and a third-party frame gets one tenth of that: exactly 104,857,600 bytes. A recorded iPhone profile reports exactly that number, together with an OPFS error that reads "The operation failed for an unknown transient reason", the error Safari's private browsing produces. Both point to a private session, not a small phone; that reading comes from WebKit's source rather than a device test.

Firefox computes its quota from the total disk size, and its private windows reject the origin private file system with a security error.

The through-line

When a browser removes a signal, detection moves to the next value that depends on the same thing. The quota stopped describing the storage, so the probe now measures how the storage behaves. For automation the consequence is the usual one: the mode, the disk and the browser version have to fit together, and a profile that is new, in memory or on unusual storage shows it in more than one value. Related: the heap limit as a RAM tell and performance.now() precision, which sets the resolution of the flush measurement.

#fingerprinting#coherence#reference

Clearcote puts this into practice

An open-source Chromium with fingerprint control compiled into the engine. A drop-in for Playwright & Puppeteer.

  • 228engine patches
  • 34,679lines added
  • 336Chromium files changed
  • 354checks per build

Engine numbers are for the Pro build; the free open build is a smaller, public patch set.

Free for one browser with GitHub. No card.