Skip to content
On this page

Realm coherence detection compares the identity a browser reports in its main page context against the identity it reports inside other JavaScript realms - Web Workers, fresh iframes, and newly created contentWindow objects. Every realm has its own copy of the global object and its own set of native functions, but all of them are backed by the same engine reading the same machine, so a genuine browser gives identical answers everywhere. Modifications applied to the page realm frequently do not propagate, which turns any disagreement between realms into a high-confidence signal.

Quick facts

Realms comparedMain window, dedicated/shared/service Workers, same-origin iframes, srcdoc iframes
Fields checkeduserAgent, platform, languages, timezone, hardwareConcurrency, GPU adapter, storage quota
Why gaps appearPage-world patches run in one realm; Workers and fresh iframes get pristine natives
Cheapest probeA blob-URL Worker that posts its navigator fields back to the page
Related failureAn iframe created after a patch may re-expose the original values

What a realm is, and why a browser has several

A realm in the ECMAScript sense is a self-contained execution environment: one global object, one set of intrinsic objects, one copy of every native function. A single tab routinely holds many. The page itself is one realm. Every iframe gets its own. Every Web Worker, Shared Worker, and Service Worker gets its own. Even an iframe with no src attribute creates a fresh realm whose contentWindow exposes a clean global.

Realms are isolated from each other at the object level - iframe.contentWindow.Array is not the same object as the page's Array, which is why instanceof famously fails across frames. But they are not isolated at the fact level. All of them ask the same engine the same questions about the same device. navigator.hardwareConcurrency in a Worker reads the same CPU as navigator.hardwareConcurrency in the page. The timezone resolved by Intl in an iframe comes from the same system setting.

That asymmetry is the entire basis of the check. Object identity differs by design; reported facts must not.

The realm checks that carry the most weight

A probe typically spawns one or two extra realms and re-reads a short list of fields. The comparisons that matter most:

  • Worker navigator agreement - userAgent, platform, languages, hardwareConcurrency, and deviceMemory read inside a Worker must match the page. A blob-URL Worker takes three lines to create and is the single highest-yield probe in this family.
  • Worker timezone agreement - Intl.DateTimeFormat().resolvedOptions().timeZone resolved in a Worker must name the same IANA zone as the page. Timezone is a common target for per-session adjustment, and Workers are a common place for that adjustment to be missing.
  • Worker GPU agreement - OffscreenCanvas gives Workers a real WebGL context, so the renderer string can be read from a second realm and compared against the page.
  • Fresh iframe contentWindow - create an empty iframe, read contentWindow.navigator, and compare. Because the realm is created after any page-load-time modification ran, it often holds the original values.
  • Storage quota across realms - navigator.storage.estimate() describes one origin-scoped quota and should report consistently from page and Worker alike.

None of these are exotic APIs. That is the point - the check is cheap enough to run on every page load.

Why realm gaps appear in the first place

The gap is structural, not accidental. JavaScript-level modification works by replacing properties on a global object - but there is one global object per realm, and code that runs in the page cannot reach into a Worker's global. A Worker is a separate thread with a separate WorkerGlobalScope; nothing the page does to window.navigator touches it. The same structural limit shows up in two related entries: why content scripts cannot hook page JavaScript and why hooks miss out-of-process iframes, where process boundaries make the isolation even harder to cross.

Closing the gap from JavaScript means intercepting every realm-creation path - Worker, SharedWorker, OffscreenCanvas, every iframe insertion, srcdoc documents, and blob URLs - and re-applying the same values inside each one before any page script observes it. That interception layer is itself observable, because the constructors doing the intercepting no longer pass native function integrity checks.

Engine-level implementations avoid the problem by construction: if the value is changed inside the browser engine rather than in page JavaScript, every realm inherits it because every realm reads from the same engine state. This is the practical reason patched Chromium and Firefox builds behave differently from injected scripts, and why a managed web data API that owns the browser layer does not expose this class of mismatch to the pages it visits.

Code example

javascript
// Compare identity across three realms: page, Worker, and a fresh iframe.
const FIELDS = ['userAgent', 'platform', 'language', 'hardwareConcurrency', 'deviceMemory'];
const read = (nav) => Object.fromEntries(FIELDS.map(f => [f, nav[f]]));

const page = read(navigator);

// Realm 2: a dedicated Worker (separate thread, separate global)
const src = `postMessage({
  ua: navigator.userAgent,
  platform: navigator.platform,
  cores: navigator.hardwareConcurrency,
  tz: Intl.DateTimeFormat().resolvedOptions().timeZone
})`;
const worker = new Worker(URL.createObjectURL(new Blob([src], { type: 'text/javascript' })));

// Realm 3: an empty iframe created right now
const frame = document.createElement('iframe');
frame.style.display = 'none';
document.body.appendChild(frame);
const inFrame = read(frame.contentWindow.navigator);

worker.onmessage = ({ data }) => {
  console.log({
    pageVsFrame:  page.userAgent === inFrame.userAgent,
    pageVsWorker: page.userAgent === data.ua,
    coresAgree:   page.hardwareConcurrency === data.cores,
    tzAgree:      data.tz === Intl.DateTimeFormat().resolvedOptions().timeZone
  });
  frame.remove();
};

Next in Coherence and lie detection · 4 of 8

Realm gaps come from patched built-ins. Here is how those get spotted.

What Is Native Function Integrity Checking?

Related terms

Concept map

Concept map

How Realm Coherence Detection connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

Why can a Web Worker see different values than the page?

A Worker runs on its own thread with its own global scope and its own pristine copies of the native objects. Code executing in the page can only modify the page realm. Unless something explicitly re-applies the same changes inside every Worker at creation time, the Worker keeps reporting whatever the underlying browser engine actually says.

Does creating an empty iframe really expose original values?

Often, yes. A fresh same-origin iframe gets a brand new realm with a fresh global object built by the engine. If a modification was applied once to the page global at load time and nothing hooks iframe creation, the new realm never receives it and reports the engine values instead.

How do engine-level browser builds avoid realm mismatches?

They change the value inside the browser engine rather than in page JavaScript. Because every realm - page, Worker, iframe - derives its answers from that same engine state, they all inherit the change automatically. There is no per-realm re-application step to forget, and no interception layer sitting in front of the native constructors.

Last updated: 2026-07-30 · Facts last verified: 2026-07-30