wall-clock and monotonic time advance at the same rate
Check id wallclock-vs-monotonic-rate
What an ordinary browser yields
Over three windows, the two clocks agree on elapsed time to well within a tolerance derived from their own measured ticks.
What a detector infers
A browser owns two clocks and they are not the same clock. Date.now() is wall time: it can be wrong, it can be corrected, it can jump. performance.now() is monotonic from navigation: it cannot go backwards, but it stops when the machine sleeps. What they must agree on is RATE — over any interval, both must report the same number of milliseconds elapsed, because they are measuring the same physics. DataDome's VM captures Date.now() minus navigationStart into one register and performance.now() into another one instruction later, and ships both raw: it never needs to trust either clock, only to subtract them. This check deliberately uses the rate form rather than the anchor form (timeOrigin plus now() reconstructing Date.now()), which two of the audit's other checks already cover. The anchor is latched from the wall clock at navigation, so a single NTP correction after the page loads poisons it permanently and no amount of re-sampling recovers; the rate form is immune to a fixed offset by construction and answers the strictly different question. Two things keep it honest. It takes the best of three windows, because performance.now() rides the platform's monotonic counter and that counter stops across suspend — close a laptop lid mid-audit and one window shows hours of skew, while a clock ticking at the wrong rate corrupts all three. And its tolerance is derived from each clock's own measured granularity rather than a constant, which is what lets Tor Browser and Firefox with resistFingerprinting — both clamping to 100ms — pass, and what makes it widen automatically for a timer-precision preference whose value we could never tabulate.
How to resolve it
A fake-timer shim, a clock offset applied to sell a timezone, or a coarsened now() meant to blunt timing attacks all hook one time source and leave the other telling the truth. Note the severity: this is a warning rather than a contradiction, because suspending the machine mid-measurement is a legitimate cause — which is exactly why the check takes the best of three windows before it says anything at all.
Read in the wild by
DataDome
Captures wall time and monotonic time ONE INSTRUCTION APART and ships both raw, so its server can subtract them. It never has to trust either clock — only that they agree.
out.txt 21636-21666 (addr 0x159ba-0x15a63): reg580 = Date.now() - performance.timing.navigationStart, reg581 = performance.now() immediately after; both registers shipped independently
xKiian/datadome-vm — DataDome bytecode VM disassemblyPerimeterX / HUMAN
Its strict backend validates relationships that exist only ACROSS the ev1/ev2/ev3 event sequence — fields that must stay constant, and clocks that must advance monotonically. A payload correct in every single snapshot is still rejected if its clocks do not reconcile across events.
gotchas.md #19: CONSTANT fields must be identical in every event; MONOTONIC fields (performance.now timers, counters, timestamps) must strictly increase across ev1 -> ev2 -> ev3
warterbili/PerimeterX_RE — ev1/ev2 field mapsEvery attribution traces to a published artifact. See the sources and their limits.
Other checks in Clocks & ledgers
- the monotonic clock and the wall clock name the same instant
epoch-vs-monotonic-clockA browser carries two physically different clocks: a monotonic counter anchored at performance.timeOrigin, and the wall clock behind… - both generations of the navigation timing API describe the same page load
navigation-timing-dual-apiThe platform kept two generations of one API alive. performance.timing speaks Unix epoch; PerformanceNavigationTiming speaks milliseconds… - a stylesheet that provably loaded appears in the performance timeline
resource-timing-vs-cssomFetching a resource writes two records: the DOM object you can see, and an immutable entry in the performance timeline. - PerformanceEntry timestamps land on the same grid performance.now() exhibits
perf-clock-grid-coherenceA timestamp's low bits are a fingerprint. DataDome ships its performance floats unrounded — 2253.7000000029802 rather than 2253.7 — and that… - every surface that reports when this document started agrees
navigation-origin-consensusOne document has one navigation start, and Chrome exposes it through four different doors: performance.timeOrigin, the legacy…
Clearcote is a browser built for fingerprint coherence
It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.
This audit takes no position on how Clearcote scores on Clocks & ledgers checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.
See the other checks in Clocks & ledgers — the family wallclock-vs-monotonic-rate belongs to.