Your Page and Your Worker Answer the Same Question — Do They Agree?
Web worker fingerprinting detection means reading device signals from inside worker contexts — Worker, SharedWorker and ServiceWorker — and comparing them with the same signals read from the page. A worker runs in its own JavaScript realm with its own navigator object, so spoofing code injected into the document never reaches it. The mismatch itself is the finding: one browser, two different answers about its own hardware.

📌 TL;DR Executive Summary
- Core Takeaway: A worker gets its own browser-filled
navigator. Detection scripts read it, diff it against the document, and treat any difference as proof of tampering. - Key Risk/Challenge: Page-level patches miss Blob and data-URL workers, OffscreenCanvas reads, SharedWorker state and ServiceWorkers that persist after the tab closes.
- Recommended Solution: Spoof below JavaScript so every realm reads one device, isolate each account in its own profile, and verify with a cross-context harness on a fresh profile.
What a Web Worker Fingerprint Actually Is
A worker is a second JavaScript realm inside the same browser. It has no DOM and no document, but it does have navigator, Intl, performance, crypto, and the canvas and audio APIs that need no element. The engine fills those objects from the same internal source it uses for the main thread.
That is what makes the comparison useful. A stock browser answers identically in both realms. A browser whose page-side values were rewritten by an extension, a userscript or a patch answers twice — and the worker’s copy is the honest one, because it came from the engine rather than from the patch. Web worker fingerprinting detection builds directly on that asymmetry: the document is the edited copy, the worker is the original.
If you are new to this class of signal, start with what a browser fingerprint is: dozens of small readings that together identify a machine, usually within a second of invisible JavaScript.
The Four Contexts That Get Asked the Same Questions
Every probe has four places to run. They share one engine and one machine, but not one set of JavaScript objects.
| Context | DOM access | Own navigator | Lifetime | What detectors read there |
|---|---|---|---|---|
| Document (main thread) | Yes | Yes | The tab | Canvas, WebGL, fonts, screen, plugins — the full set |
| Dedicated Worker | No | Yes | While its script lives | User agent, platform, languages, cores, memory, timezone, OffscreenCanvas, audio |
| SharedWorker | No | Yes | Shared across tabs of one origin | The same values, compared across those tabs |
| ServiceWorker | No | Yes | Survives the tab closing | Cached values and intercepted requests replayed into later visits |
What WorkerNavigator Exposes Without a DOM
Inside a worker you can read navigator.userAgent, platform, language and languages, hardwareConcurrency, deviceMemory where implemented, onLine and a storage estimate. You also get performance.now() for timing, Intl for timezone and locale, and Web Crypto.
The surface is narrower than the document’s — no screen size, no window geometry, no plugin list — but it is stable. That narrowness is why a conflict is loud: there are only a handful of values, and every one of them should agree.
7 Ways Worker Contexts Give You Away
Detection is a set of techniques, not a single one. These are the seven readings a worker-context probe collects, and each is covered in detail below.
- Navigator mismatch. The page reports one user agent, platform, language list, core count and memory figure; the worker reports another.
- Timezone and locale drift. The worker’s
Intl.DateTimeFormat().resolvedOptions().timeZoneshould match the proxy’s exit region, not the host machine. - OffscreenCanvas readback. Text and gradient pixels read through
convertToBlob()with noHTMLCanvasElementanywhere in the loop. - Audio off the main thread. The same oscillator graph rendered inside a worker’s
OfflineAudioContext. - Timing divergence.
performance.now()measured in the worker, checked against the renderer the page claims. - Blob and data-URL construction. No script file request, so a blocker never sees the worker being created.
- ServiceWorker and SharedWorker residue. Values held in a registration or a shared context, replayed into a later visit or a different account.
How Worker Fingerprinting Works Under the Hood
None of this needs a script tag in the markup. The detector builds the worker at runtime, so the probe can run before any blocker sees a request. That is usually the first surprise in web worker fingerprinting detection — there is no script URL to intercept.
Three Ways to Create a Worker
- A script URL:
new Worker('/fp.js')— a real network request an extension can see and block. - A Blob URL:
new Worker(URL.createObjectURL(blob))— the source is assembled in memory, so there is no second request to intercept. - A data URL: the source is passed to the constructor as a string, with the same effect.
That is why wrapping the Worker constructor at document_start does not close the gap. A wrapper covers construction through window.Worker only. SharedWorker has its own constructor, service workers register through navigator.serviceWorker, and a worker can spawn a nested worker from inside itself.
Even when the wrapper catches the call, it cannot hand the new realm the patched prototypes of the document. The worker receives its own pristine navigator, and page-level patches stop at the realm boundary.
OffscreenCanvas: Canvas Reads With No Canvas Element
A worker can create an OffscreenCanvas, draw text and gradients into it, and read the pixels back with convertToBlob() or transferToImageBitmap(). No HTMLCanvasElement exists at any point, so protection that hooks only HTMLCanvasElement.prototype never fires.
The output varies by GPU model, driver version, OS font rendering and anti-aliasing — the same reasons a normal canvas hash differs between machines. For the mechanics behind that reading, see the guide to canvas fingerprinting.
Recent engine bug reports show the gap is real: canvas protections in at least one browser engine did not apply inside shared and service workers for a period, and Firefox tracked its own OffscreenCanvas gap as a bug.
Audio and Timing Off the Main Thread
AudioContext and OfflineAudioContext with oscillator nodes run inside worker contexts, so audio can be sampled without touching the page thread. Audio still works as a signal in 2026 at medium entropy — the differences are small, but they track hardware.
Timing behaves the same way. performance.now() in a worker measures the same scheduler and the same silicon as the page. If the document advertises a discrete GPU while shader execution timing matches integrated graphics, that divergence is measurable.
The Cross-Context Harness
This Playwright script attaches to a running browser profile and asks the document and a Blob-backed worker the same six questions. Every line that prints MISMATCH is a detectable inconsistency.
from playwright.sync_api import sync_playwright
CDP_URL = "http://127.0.0.1:PORT" # copy it from the profile's automation settings
PROBE = """
async () => {
const source = `self.onmessage = () => {
postMessage({
ua: navigator.userAgent,
platform: navigator.platform,
cores: navigator.hardwareConcurrency,
memory: navigator.deviceMemory || null,
langs: navigator.languages.join(','),
tz: Intl.DateTimeFormat().resolvedOptions().timeZone
});
};`;
const url = URL.createObjectURL(new Blob([source], { type: 'application/javascript' }));
const worker = new Worker(url);
const fromWorker = await new Promise((resolve) => {
worker.onmessage = (event) => resolve(event.data);
worker.postMessage(null);
});
worker.terminate();
URL.revokeObjectURL(url);
const fromPage = {
ua: navigator.userAgent,
platform: navigator.platform,
cores: navigator.hardwareConcurrency,
memory: navigator.deviceMemory || null,
langs: navigator.languages.join(','),
tz: Intl.DateTimeFormat().resolvedOptions().timeZone
};
return { fromPage, fromWorker };
}
"""
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(CDP_URL)
context = browser.contexts[0] if browser.contexts else browser.new_context()
page = context.pages[0] if context.pages else context.new_page()
page.goto("https://example.com")
result = page.evaluate(PROBE)
for key, page_value in result["fromPage"].items():
worker_value = result["fromWorker"][key]
flag = "match" if page_value == worker_value else "MISMATCH"
print(f"{flag:9} {key:8} page={page_value!r} worker={worker_value!r}")
# do not call browser.close() here: over CDP it shuts down the profile's browser
Run it against a real site, not about:blank, then run it again a few minutes later. Values that stay stable but differ from a clean machine are a configuration problem. Values that change between runs are worse: randomization signatures are exactly what ML-based systems such as Cloudflare, PerimeterX and DataDome are built to recognize.
Why a Cross-Context Mismatch Flags You Faster Than a Dirty Fingerprint
A rare but consistent fingerprint reads as an unusual machine. A page that claims one set of hardware while its own worker claims another reads as a manipulated browser. That difference matters, because a mismatch is a tampering result rather than a statistical inference drawn from entropy — and it lands in a different risk category than an odd-looking but truthful device.
CreepJS is the best-known public implementation of web worker fingerprinting detection: it collects in the main thread, in a Worker, in a SharedWorker and in a ServiceWorker, cross-references the results, and flags prototype lies plus the lie patterns that recur across contexts. Use only the official CreepJS deployment; mirrors of the same test have been used as honeypots.
Detection in 2026 is described as layered risk assessment — device entropy, behavioural modelling, network intelligence and cross-session graph analysis combined — rather than a static list of parameter checks. Worker values serve that model well: they are cheap to collect, hard to fake consistently, and comparable across sessions.
Service Workers Outlive the Tab
A registered service worker persists after the tab closes and intercepts network requests. A fingerprint cached there can be replayed into later visits, so a value you corrected yesterday may still be served today from a background script — and read against a page in a different account. Firefox 156 added a per-site DisableServiceWorkers policy so administrators can stop chosen sites from registering or using them, per the Firefox release notes. The wider persistence layer is covered in this walkthrough of service worker fingerprinting.
The Layer Below JavaScript
Everything above runs in JavaScript, and JavaScript only starts after the connection is already open. During the TLS handshake the client sends its cipher suites and their order, its extensions and their order, its supported groups, signature algorithms and ALPN protocols. JA4 hashes those values into a fingerprint rated high entropy and very high durability, precisely because page code cannot rewrite the handshake.
So a clean worker story still sits on top of a transport story. Only the browser build you actually run decides what goes on the wire.
Who Worker-Context Leaks Affect Most
The more identities you run from one machine, the more a repeated worker signature costs you. Worker values are narrow and stable, which makes them excellent join keys.
Multi-Account Sellers and Marketplace Teams
If ten seller accounts report identical core counts, identical device memory and identical timezone offsets from the worker realm, they look like one machine with ten logins. Account graphs get built from exactly that kind of repetition, and it survives any amount of cookie hygiene.
Ad Buyers and Media Buyers
Ad platforms score the client as well as the click. When one worker signature appears across dozens of ad accounts, those accounts stop being independent signals. The symptom is usually quiet: filtered traffic, weaker delivery, extra verification steps.
Agencies, Social Media Managers and Automation Developers
SharedWorker state and service worker registrations belong to a profile and an origin, not to a login. If two clients’ accounts run in the same profile, a value cached in one session can be read against the other. Scripted browsers have the mirror-image problem: launch flags usually cover the user agent and the viewport while worker values stay at engine defaults, so the page looks configured and the worker looks untouched.
A Practical Checklist for Testing Worker-Context Leaks
- Run the harness above against a real site, in the profile you actually use rather than a throwaway one.
- Compare every shared value — user agent, platform, languages, core count, device memory, timezone and locale. Any difference is a finding.
- Compare canvas twice. Draw the same shapes and text through a page canvas and through an OffscreenCanvas in a worker. If only the page is randomized, you have found the gap.
- Compare audio the same way, rendering the same oscillator graph in an OfflineAudioContext on the page and inside a worker.
- Audit service worker registrations before you trust a profile; a leftover registration from another session is residue.
- Test from a fresh profile, because changing a setting does not clear registrations that already exist.
- Re-run after a few minutes. Identical values mean stable; new values on every run mean your setup is advertising its randomization.
scopes = page.evaluate(
"async () => (await navigator.serviceWorker.getRegistrations()).map(r => r.scope)"
)
print(scopes) # an empty list is what you want on a fresh profile
If you want to compare the test surfaces themselves before committing to a workflow, this roundup of browser fingerprint test tools covers what each one can and cannot see across contexts.
Common Mistakes That Keep Worker Fingerprints Mismatched
- Patching prototypes in the page and assuming workers inherit them. Each realm is built by the engine and starts clean.
- Wrapping only
window.Worker. SharedWorker, service worker registration and nested workers each reach a realm your wrapper never sees. - Treating canvas protection as finished once
HTMLCanvasElementis hooked, while OffscreenCanvas readback goes straight past it. - Reusing one profile across accounts. Service worker and SharedWorker residue carries values between sessions you consider separate.
- Randomizing values on every visit. A fingerprint that changes each time is its own signature, and ML-based detectors are trained to spot that pattern.
- Relying on user agent edits. The UA string has been frozen in Chrome since Chrome 107 in late 2022, and
navigator.pluginsandnavigator.platformnow return generic or empty values. - Treating a beta fingerprint plugin as production infrastructure. Plugins of that kind tend to be limited to one OS and ship with their own bug warnings; they are not a detection fix.
- Ignoring the transport layer. Perfect worker values on a mismatched TLS handshake still describe two different clients.
Where Send.win Fits
Sendwin Browser is a patched-Chromium desktop app for Windows, macOS and Linux, with cloud profiles that run on EU and US nodes from any device. The detail that matters for web worker fingerprinting detection is where the spoofing happens: canvas, WebGL, audio, fonts and hardware are handled at the engine level rather than by overriding JavaScript objects in the page.
That placement changes the worker picture. Because the synthetic device is defined below JavaScript, a worker realm reads the same values as the document instead of a pristine set the patch never touched. No two profiles share a fingerprint, so the narrow worker values that make such good join keys stop lining up across accounts.
Coherence extends past the realm boundary: timezone, locale, WebRTC and geolocation follow the proxy exit IP automatically, so what a worker reports through Intl agrees with the address the site sees. Every plan includes built-in residential proxies, and HTTP/SOCKS5 proxies are supported if you bring your own. Isolation handles persistence — each profile is its own environment, so service worker registrations and SharedWorker state do not leak between the accounts you keep apart. Paid plans add cross-device cloud sync and profile sharing that opens already signed in, and the local Automation API for Selenium, Puppeteer and Playwright is on Team, the plan you need to drive the harness above against a live profile.
🏆 Send.win Verdict
Worker fingerprinting detection is unforgiving because it does not need a rare machine — it only needs a contradiction. Fixing the page while leaving the worker untouched guarantees that contradiction, and patching harder in JavaScript only adds more objects that can disagree. Send.win removes the contradiction where the values are actually produced: one device model per profile, read identically by the document, the worker and everything the engine spawns, with profile boundaries that keep service worker residue out of your other accounts.
Try Send.win free today — run the cross-context harness on a clean profile and compare the worker rows yourself; the desktop trial is $0 for 30 days and the cloud preview needs no install.
Frequently Asked Questions
Does a web worker inherit fingerprint spoofing from the main page?
No. A worker runs in its own JavaScript realm with its own navigator object, populated by the browser, and it does not inherit patches injected into the document. Unless the spoof is applied below JavaScript, the worker reports the real underlying values while the page reports the edited ones.
How do detection scripts compare worker and document fingerprints?
They run the same probe twice — once in the page, once in a worker built at runtime — and diff the results. That diff is web worker fingerprinting detection in its simplest form: keys like user agent, platform, languages, hardware concurrency, device memory and timezone should be identical, and any difference is recorded as tampering rather than as a hardware trait.
What can a worker read from navigator without the DOM?
userAgent, platform, language and languages, hardwareConcurrency, deviceMemory where implemented, onLine and a storage estimate. It also gets performance.now(), Intl and Web Crypto. Screen dimensions and window geometry are unavailable, which is why the remaining values are so easy to compare.
How does OffscreenCanvas fingerprinting bypass canvas protection?
The worker creates an OffscreenCanvas and draws into it with no HTML canvas element involved, then reads pixels back through convertToBlob() or transferToImageBitmap(). Protection that hooks only HTMLCanvasElement.prototype never fires, because that prototype is never touched.
Can a Blob URL worker avoid extension interception?
Yes. When the worker source is assembled in memory and passed to the constructor as a Blob or data URL, there is no separate script request for an extension or blocker to inspect. Only construction through window.Worker can be intercepted at all, and even then the new realm starts clean.
Do service workers persist fingerprints after the tab closes?
They persist until they are unregistered. A service worker intercepts network requests and can hold cached values that are replayed into later visits, so a signature you changed today may still be served tomorrow from a background script. Firefox 156 added a per-site DisableServiceWorkers policy to block that for chosen sites.
How do I test my own setup for worker fingerprint leaks?
Run a harness that evaluates the same probe in the document and in a Blob-backed worker, then compare every shared key. Repeat it from a fresh profile, check service worker registrations, and re-run after a few minutes to see whether the values stay stable. Use only the official CreepJS deployment for a second opinion; mirrors have been used as honeypots.
How does CreepJS detect prototype lies in worker contexts?
It collects fingerprints in the main thread, in a Worker, in a SharedWorker and in a ServiceWorker, then cross-references them and looks at lie patterns — how a patched object behaves oddly across contexts. Validation across many visitors turns individual inconsistencies into recognisable tampering signatures.
How Send.win Helps With Web Worker Fingerprinting Detection
Send.win is an antidetect browser built for exactly this kind of work — every profile is a clean, isolated identity:
- Isolated profiles – unique fingerprint, separate cookies and storage per profile
- Stealth engine – canvas, WebGL, fonts, and audio spoofed at the engine level
- Desktop app + cloud sessions – native app for Windows, macOS, and Linux, or run profiles in the cloud with no install
- Built-in residential proxies – with automatic timezone, locale, and WebRTC matching
- Team features – share logged-in profiles with teammates without sharing passwords
Try the instant cloud browser demo — no install, no signup — or download the desktop app. The 30-day free trial needs no credit card, and paid plans start at $6.99/month billed annually (see pricing).