FROST on Mobile Browsers: A Real Timing Signal, and a Storage Artefact That Looked Like One.
Author: Clayton Bax
Onyx Digital Intelligence. Browser fingerprinting has relied on canvas, WebGL, audio, fonts, and hardware signals for years. Recent academic research (Weissteiner et al., Graz University of Technology, presented at DIMVA 2026) demonstrated that storage timing differences in the Origin Private File System (OPFS) can add measurable entropy, without any user interaction or special permission. The original work was tested on desktop macOS. No published results exist for mobile browsers.
And that's where I come in.
This post documents an independent replication and extension on mobile Android, across multiple test runs including one long-duration run that revealed something the shorter runs had missed.
Test Setup
Device: Samsung Galaxy A56 5G, Android 16 (One UI 8.5) Browsers tested: Customised hardened Brave and stock Chrome.
Test page:
onyxdigitalintel.netlify.app, an OPFS timing probe.
Method:
A Web Worker using a synchronous FileSystemSyncAccessHandle, alternating idle and load blocks, with a second worker generating storage contention through repeated fill-swap-fill cycles.
crossOriginIsolated:
Confirmed true, achieved via a Netlify _headers file. GitHub Pages cannot serve the custom headers this requires, which is why earlier attempts at this setup failed before moving to Netlify.
What the Data Shows
Two distinct phenomena, not one, a stable, low-amplitude contention signal. Across independent runs in both browsers, load blocks showed a consistent median timing increase of roughly 130 to 175 microseconds over the idle baseline. This held steady block to block, with no drift across a run; this is the FROST channel itself.
A separate, episodic high-amplitude spike. In a sustained 60-block run, a small number of load blocks showed median timings jumping to 1,585 to 1,740 microseconds, with p99 latencies exceeding 20,000 microseconds. These spikes switched on abruptly around 26 seconds into the run, recurred intermittently, then receded back to the normal range, before reappearing near the end of the run.
The idle baseline stayed flat throughout, even during the spike blocks. A pure thermal effect would show a steady climb as the device warms. This pattern, on, off, on again, with idle readings unaffected in between, is more consistent with periodic storage housekeeping, such as SSD garbage collection or a kernel writeback cycle, than with the contention channel itself.
A Revised Hypothesis
Earlier in this work, the operating theory was that Brave's timing-resistance mitigations suppress the FROST signal relative to stock Chrome.
Testing across this round does not support that. The stable signal appeared in both browsers at a comparable order of magnitude. The more accurate read is that the storage subsystem, not the browser engine, is the dominant factor in what gets measured here.
A Methodology Note
Browser user agent strings on this device report "Android 10." That is not the device's actual OS, and not a detection lag either.
Since Chrome 110 (February 2023), Chrome's User-Agent Reduction policy hardcodes the Android version field to "Android 10" and the device model to "K" for every Android device, regardless of what is actually installed, specifically to prevent OS and device fingerprinting. Brave inherits this behaviour, since it runs on the same Chromium engine. The device used in this testing runs Android 16. It is worth stating plainly, since getting a device's own OS version wrong in a technical writeup undermines everything else in it, even when the wrong value is the browser's doing rather than the researcher's.
What's Still Open
Further controlled runs are needed to separate genuine browser-level variance from device and storage variance.
The exact trigger for the high-amplitude spikes—elapsed time, cumulative bytes written, or the delete-recreate cycle in the contention worker—is not yet isolated.
An OPFS constraint
It occasionally causes one worker's sync access handle to conflict with another's on the same file, dropping a small number of reads per run. This is a known limitation of the API, not a measurement error, but it does reduce the clean block count in some runs.
Full test harness and raw logs:
github.com/Baximus855/opfs-probe
Open to work: Onyxdigitalintelligence85@protonmail.com