AudioContext invariants + a deterministic offline render
Check id audio-context-coherence
What an ordinary browser yields
A standard sampleRate, a small positive baseLatency, channelCount within maxChannelCount, channelInterpretation "speakers", and two identical offline renders.
What a detector infers
The check constructs a live AudioContext and reads four invariants — sampleRate, baseLatency, the destination node's channelCount against its maxChannelCount, and channelInterpretation — then renders a fixed OfflineAudioContext graph twice and compares the two results. Each assertion has a physical basis. sampleRate is the hardware's real rate and must be one of the standard values (44100 and 48000 dominate on desktop); baseLatency is a small positive fraction of a second; a destination cannot present more channels than its own maximum, and its interpretation defaults to "speakers". The determinism test is the sharpest: the offline audio graph is a pure function of its inputs on a real engine, so two identical renders must be byte-identical, and when they differ it means the audio stack is injecting per-call noise — the same anti-fingerprinting technique the audio hash exists to expose. A detector reading an impossible sampleRate, a latency or channel layout that cannot occur, or two disagreeing renders infers a synthesised or noised audio stack. Kasada's audio_context_sample_rate, base_latency, channel_count, channel_count_mode and channel_interpretation probes plus its audio_fingerprint read exactly this surface. N/A where Web Audio is unavailable.
How to resolve it
Leave the Web Audio stack native. Do not add per-call noise to OfflineAudioContext output — it makes two identical renders disagree, which is itself the tell. Keep sampleRate and the channel layout consistent with the host's real audio device.
Read in the wild by
PerimeterX / HUMAN
Collects an audio fingerprint from an OfflineAudioContext(1, 44100, 44100): a 10 kHz sine oscillator through a fixed-parameter DynamicsCompressor, rendered once, with channel-0 samples 4500-4999 summed by absolute value — the raw sum and a hash of it are both sent. It reads the render output only, not the context's declared parameters.
main.min.js (iFood deployment) `zY()`: `var n=new(hS.OfflineAudioContext||hS.webkitOfflineAudioContext)(1,44100,44100);…var e=n[hQ(469)]();…e.type="sine",zZ(e.frequency,1e4,r);var h=n.createDynamicsCompressor();zZ(h.threshold,-50,r),zZ(h.knee,40,r),zZ(h.ratio,12,r),zZ(h.reduction,-20,r),zZ(h[hQ(470)],0,r),zZ(h.release,.25,r),e.connect(h),h[hQ(471)](n.destination),e.start(0),n.startRendering().then((function(n){var e=0;…for(var r=4500;r<5e3;r++){var h=n.getChannelData(0);h&&(e+=Math.abs(h[r]))}var i=e.toString(),o=i&&iR(i);t({"EX1kN1ccYQI=":i,"Dh47VEt6OW8=":o})}))` — string table decodes hQ(469)='createOscillator', hQ(470)='attack', hQ(471)='connect', hQ(472)='getChannelData'. Both values appear in captured decoded payloads: `"EX1kN1ccYQI=":"126.86972438948578"` (the raw sum) and `"Dh47VEt6OW8=":"2dce8c55c6897067fdf0c76ddf6e6d50"` (its hash). The same routine appears in other PX deployments as `wC()` (Walmart, with a literal `createOscillator()`) and `Ar()` (Total Wine) with identical compressor parameters.
Kasada
Dedicates a whole batch to the audio stack — not just the fingerprint hash but the context's own declared parameters.
audio_fingerprint, audio_context_sample_rate, audio_context_base_latency, audio_context_output_latency, audio_context_channel_count, audio_context_channel_count_mode, audio_context_channel_interpretation, audio_context_max_channel_count
Kasada ips.js teardown — the full 427-probe listEvery attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Environment & locale
- every Intl constructor resolves the same locale
intl-locale-coherenceEvery Intl constructor — NumberFormat, DateTimeFormat, PluralRules, Collator — resolves its locale through the same underlying ICU data, so… - navigator.plugins is a real platform object (structured-clone probe)
plugins-structured-cloneThe structured-clone algorithm is implemented in C++ and interrogates objects about what they actually are, not what they claim. - canPlayType answers identically for <video>, <audio> and the prototype
canplaytype-element-agnosticcanPlayType is implemented exactly once, on HTMLMediaElement.prototype, so <video> and <audio> inherit the same C++ function. - canPlayType behaves like a real codec parser (bogus + quoting)
canplaytype-parser-trapsA real canPlayType is an RFC-6381 parser, not a dictionary. This check exercises two properties only a parser has. - the analyser byte spectrum is the defined quantisation of its float spectrum
audio-analyser-float-vs-byteAn AnalyserNode publishes the same spectrum twice. getFloatFrequencyData returns decibels, and getByteFrequencyData returns that spectrum… - speech synthesis voices (host OS speech engine)
speech-voicesspeechSynthesis.getVoices() enumerates the host operating system's speech engine — a resource stealth stacks essentially never touch, which… - the voice list arrives the way Chrome delivers it
speech-voices-async-pathspeech-voices reads WHAT is in the voice list. This reads HOW the list arrived, which is a separate fact and survives a roster whose every… - an OS that ships a speech synthesiser enumerates at least one voice
speech-voices-vs-platformThe plain speech-voices readout above refuses to score an empty list, and as written it is right to: Chrome does not build the voice list in…
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 Environment & locale 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 Environment & locale — the family audio-context-coherence belongs to.