Skip to content
All fingerprint checksEngine & OS oracles

the three heap numbers describe a possible heap

Check id jsheap-triplet-ordering

What an ordinary browser yields

used ≤ total ≤ limit. Absent entirely on Firefox and Safari, which have no performance.memory — that is not a finding, the API simply does not exist there.

What a detector infers

This check replaces a retired one, and the swap is the lesson. The old check asked whether performance.memory.jsHeapSizeLimit was one of a handful of values V8 was known to use — a question answered against a table maintained by hand. It rotted exactly as such tables do: a stock Chrome 150 reports 4395630592, the table had never seen it, and the audit told an ordinary browser it was implausible. This asks a different question entirely: do these three numbers describe a possible heap? usedJSHeapSize counts the live bytes, totalJSHeapSize the bytes allocated to hold them, and jsHeapSizeLimit the ceiling neither may cross — so used must fit inside total, and total must fit under limit. That is answerable from the MEANING of the fields, needs no reference data, and will still be answerable in ten years. Magnitudes are never inspected, only ordering, and that restraint is deliberate: live Chromium reports a limit of exactly 2^32, precisely the suspiciously round number a magnitude check is tempted to reject as fake. It is real. The ordering also survives V8's value bucketing, because the quantisation is monotonic — rounding can move the numbers but cannot invert the pair — and the triplet is frozen within a synchronous task, so there is no sampling skew between the three reads. PerimeterX ships all three as separate fields and lets its backend difference them; the repo's own forged template declares a used size LARGER than its total, which is what a value invented field by field looks like from the outside. Anyone assembling a fingerprint has to keep related fields related, and independently-rolled numbers forget they were ever related.

How to resolve it

These are three views of one heap rather than three independent numbers. If you are setting them, keep the relation: live bytes inside allocated bytes, allocated bytes under the ceiling. Better, leave performance.memory native — V8 quantises the values already, and its rounding is monotonic, so a real heap can never invert the ordering no matter how the numbers are bucketed.

How browser stealth is measured: coherence, entropy, and headless tells

Read in the wild by

PerimeterX / HUMAN

Reads all three performance.memory counters, ships each as its own field in the collector payload, and separately re-reads the triplet later to report whether any of the three changed since page load.

PerimeterX collector main.min.js (ifood.com.br build, posts to collector-a.px-cloud.net). Snapshots the triplet once at init: `function qG(){try{var t=hS.performance&&hS.performance.memory;t&&(pk=t.jsHeapSizeLimit,pl=t.totalJSHeapSize,pm=t.usedJSHeapSize)}catch(t){}}` — qG() is called from the init routine `function qL(){qI(),qM(),qF(),qH(),qG(),...}`, and pk/pl/pm have no other write site in the bundle. `Co()` then ships each counter as its own payload field: `n&&(t[hQ(800)]=n[jb("dXNlZEpTSGVhcFNpemU=")],t["WipvIBxKaBc="]=n[jb("anNIZWFwU2l6ZUxpbWl0")],t["EX1kN1cQZQY="]=n[jb(hQ(801))])` — the base64 literals decode to usedJSHeapSize / jsHeapSizeLimit / totalJSHeapSize, hQ(800) resolves to field key "NABBSnJgQXE=" and hQ(801) to b64 'totalJSHeapSize'; observed in a captured payload as 106414918 / 4294967296 / 224456166. `Ba()` re-reads all three and returns whether any moved from the init snapshot: `if(t[-11]=hS[hQ(199)]&&hS.performance.memory,t[-11])return pk!==t[-11].jsHeapSizeLimit||pl!==t[-11].totalJSHeapSize||pm!==t[-11][hQ(691)]` (hQ(199)='performance', hQ(691)='usedJSHeapSize'), shipped as `t[0]["fg4LRDtuCHI="]=Ba()`, observed value true.

Kasada

Reads all three V8 heap counters as separate probes — the triplet, not just one number.

js_heap_size_limit, used_js_heap_size, total_js_heap_size

Kasada ips.js teardown — the full 427-probe list

Every attribution traces to a published artifact. See the sources and their limits.

Other checks in Engine & OS oracles

Who builds this test

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 Engine & OS oracles 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 Engine & OS oracles — the family jsheap-triplet-ordering belongs to.