TL;DR: Research-grade anti-fingerprinting pipeline (Python + JavaScript) injecting deterministic, seed-based patches for Canvas/WebGL/WebGPU/Fonts/Headers via CDP.
Runs on Windows with VPN (ProtonVPN/OpenVPN) + optional proxy
Browser Anti-Fingerprinting: Python + JavaScript
Russian version: see Readme_RUS.md.
The system has been designed to evaluate and mitigate modern browser fingerprinting surfaces (Canvas 2D/OffscreenCanvas, WebGL/WebGPU, Fonts, UA-CH/Headers).
The project demonstrates that isolated fingerprint randomization is largely ineffective. Browser fingerprint mitigation only becomes meaningful when multiple observable surfaces are controlled as a consistent system rather than patched independently.
Python (Selenium + undetected_chromedriver) + JavaScript patches (modules) injection via CDP to control fingerprint surfaces. Mitmproxy is optional and is switched directly in main.py.
- FernwehContext hidden state - Defines canonical hidden state, module slots, and owner routes.
- Core apply methodology - Documents preflight, wrapper, apply, rollback, and native-reference handling.
- Function.prototype.toString essentials - Defines native-looking function shape and realm-local synchronization rules.
- PRNG seed and global_seed - Documents seed creation, transfer, canonical PRNG ownership, and consumer rules.
- Worker scope hidden state - Defines Dedicated, Shared, and Service Worker parity without cross-realm object sharing.
- Public API implementation policy - Preserves descriptor shape, receiver checks, native error paths, and proxy observability.
- Pipeline entity typology - Classifies containers, state owners, wrappers, carriers, and diagnostic entities.
- Method surfaces methodology - Maps method and accessor surfaces to their normative installation routes.
- Promise and entry/result accessor methodology - Extends the surface rules for asynchronous and accessor-driven paths.
- Hooks methodology - Describes Canvas/WebGL hook registration and execution boundaries.
- WebGL critical paths - Maps WebGL values, hooks, and diagnostic routes.
- Headers pipeline - Coordinates browser preferences, CDP, JavaScript, and optional mitmproxy headers.
- Fonts compound - Documents generated font assets, runtime loading, Canvas interaction, and deterministic behavior.
- RectsPatchModule is a window-realm module that influences DOM/SVG layout and rendering inputs, which are subsequently measured by native Chromium via
DOMRect,DOMRectList,Element.getBoundingClientRect(),Element.getClientRects(),Range.getBoundingClientRect(),Range.getClientRects(), and SVG layout/geometry APIs. - Guard flag - Defines guard lifecycle.
- DEGRADE diagnostics - Defines the unified observable diagnostics channel and failure classes.
- DEGRADE module template - Provides the module-level diagnostic projection template.
- DEGRADE calls summary - Inventories current diagnostic call sites across the pipeline.
The tools are intended for testing, debugging and research purposes only. Do not use to bypass security controls or violate site policies or laws.
This is a privacy research tool and detector resilience tester. Remember that you are responsible for how you use it.
This is not a "silver bullet" or an anonymity toolkit. Modern FP techniques (TLS fingerprints, behavioral checks, WebGL/WebGPU constraints, AudioContext API updates, etc.) can correlate sessions and deanonymize environments even with patches. Partial control of the surface does not provide full anonymity.
The primary objective of the project was to conduct a comprehensive examination of the browser from an internal perspective, with the aim of identifying the APIs in use and any data-leak channels that may be present.
Understand how fonts, WebGL, Client Hints, plug-ins and other components affect the fingerprint.
Maintain oversight of fingerprinting and its replication through profiling and network layer management.
There is no hardcoding: profile values are assembled from dictionaries/profile data and exposed through window.__* only as bootstrap/bridge inputs. The actual patch logic, helpers, and intermediate state are progressively moved into module-local closure state instead of being kept as long-lived public globals.
MDN/Chromium compatibility: hooks stay within native API boundaries, with closure-owned routing/helpers used to preserve receiver/owner contracts and avoid Illegal invocation.
__GLOBAL_SEED / DPR / device metrics: synchronized through initialization variables.
Research / non-commercial; released “as is”. No stability guarantees.
Built by a single author — scenario/OS coverage is limited. Forks and contributions are welcome.
See Issues/TODO for applicability limitations.
Executed only on Windows + ProtonVPN (OpenVPN CLI). Other OS/VPNs not tested.
In sum, the pipeline is being initialised and the script is being executed. The designated tasks are being carried out, just several surfaces have been patched.
The Unlicense (Public Domain). Copyright and related rights are waived
to the extent possible. You may copy, modify, publish, use, compile, sell,
and distribute, with or without attribution. Software is provided “AS IS”.
OS: Windows 10/11 (OpenVPN path assumes Windows).
Python: 3.12 (3.11+ recommended).
OpenVPN installed locally (default path is set in vpn_utils.py, can be changed).
mitmproxy (in requirements.txt, only needed when the mitmproxy switch is ON).
Chrome/Chromium — local copy of Chrome for Testing path configured in main.py.
All Python deps are pinned in requirements.txt.
There is one entrypoint: main.py.
Mitmproxy is controlled by a small visible switch near the top of main.py:
MITMPROXY_ON = True
# MITMPROXY_OFF = TrueThis means: run with mitmproxy. main.py starts mitmdump, sends Chrome through 127.0.0.1:8082, and stops the process on exit.
To run without mitmproxy, flip the two lines:
# MITMPROXY_ON = True
MITMPROXY_OFF = TrueIn this mode, main.py does not start mitmdump, does not set Chrome proxy options, and Chrome connects directly.
Only one line must be active. If both are active or both are commented, startup fails immediately with a clear error.
✔ Easier browsing without immediate CORS/header challenges.
✔ Direct visibility into CORS/headers/Client Hints.
✖ Requires mitmproxy.
✖ Detectors may identify mitmproxy TLS fingerprint as not "native".
✔ Simpler and lighter startup.
✔ No local proxy layer.
✖ CORS limitations may reduce effectiveness for real browsing; this mode is better as a direct browser/CDP/JS pipeline.
Note: VPN usage is enforced in both modes unless you disable the VPN calls separately.
The script can be run without controlling OpenVPN. In this case, you can:
✔ Use any other VPN client (including one controlled via a graphical interface).
✔ Or work without VPN at all.
To accomplish this, just comment calls to the VPNClient instance methods responsible for VPN authentication, setup, and connection in def main():
# client.verify()
# client.prepare()
# logger.info("Preparation completed")
# client.connect()
client.post()
In this mode, the script works exactly as before, performing all subsequent steps without stopping or starting OpenVPN processes.
If you already have a VPN set up in any other way, the script will simply use the current network environment.
- main.py - runtime entrypoint and orchestration layer.
- assets/scripts/window/core - window bootstrap, Core, context, PRNG, logging, and probes.
- assets/scripts/window/patches - window-side graphics, media, navigator, and stealth patches.
- assets/scripts/workerscope - Dedicated, Shared, and Service Worker bootstrap and patching.
- assets/generated_fonts - bundled generated font assets and indexes.
- assets/templates/font_patch.template.j2 - template for generated font patch output.
- profile_data_source - profile dictionaries and platform/browser data.
- tools/generators - font generation and CDP worker/service-worker helpers.
- tools/tools_runtime - runtime helper modules and header/CORS support.
- tools/tools_infra - VPN and Python-side diagnostic helpers.
- requirements.txt - pinned Python dependency list.
- main.py - main orchestration entrypoint: builds/loads profile data, prepares CDP injection payloads, launches Selenium + undetected-chromedriver, wires runtime helpers, and applies the staged JS pipeline to window and worker scopes.
- profile_data_source - source dictionaries and base profile data for platform/browser composition: Win32/MacIntel shells, browser-version maps, plugin sets, permissions setting, and profile defaults.
- rand_met.py - fonts pipeline: prepares per-platform generated fonts, cache metadata, and font_patch.generated.js from the Jinja2 template.
- cdp_catapult.py - CDP-side payload assembly and
ServiceWorkerdelivery helper used by the runtime injection flow. - cdp_worker_env.py - attaches to
DedicatedWorkerandSharedWorkertargets over CDP and applies the same environment overrides used by the main page, keeping worker-sideuserAgent,language,languages,platform, andhardwareConcurrencyaccessor variables aligned before execution resumes. - handle_cors_addon.py - mitmproxy runtime addon for CORS/preflight handling, service-domain filtering, and request/response-side header coordination.
- headers_adapter.py - realistic Accept/header shaping by browser brand/version.
- helpers.py - shared runtime/profile helpers used by main.py and the injection pipeline.
- vpn_utils.py - VPN lifecycle and region-aligned setup using
.ovpnfiles from cfg_vpn. - overseer.py - Python-side logging/diagnostic helper.
JavaScript (assets/scripts/window/core)
- core_window.js - provides the foundational layer for all other window-related modules and initializes the shared
Coreinfrastructure. It contains the key runtime mechanisms: common wrappers,Core.applyTargets, safe descriptor installation, native shaping andtoStringmasking, theinvalid-thiscontract, and diagnostic utilities. It is also the place wheresafeDefine, the wrapper factory formethod/accessor/ctor, and the logic for preserving the API's native-looking surface are implemented. Defines the contract-driven patching engine throughCore.applyTargets. Downstream modules rely on it as the support layer preserving native behavior and appearance, correct handling ofinvalid receiverand other engine-level errors while maintaining the expected native pass-through semantics. - context.js - acts as an orchestration layer for
Canvas/WebGL: it assemblesFernwehHooks, validates the presence of hooks exported from respective modules, then registers them in a unified hook queue. It also acts as a patching gateway: it wrapsgetContext,toDataURL,toBlob,convertToBlob,CanvasRenderingContext2Dmethods, andWebGLprototypes so that downstream modules pass through a single point of application, preventing proxy leaks,thisloss, and broken native descriptor mechanics. - prng_seed.js - gateway for
__GLOBAL_SEEDfrom the Python backend to the JavaScript environment. Module installs seed-driven PRNG state and exposes the deterministic seed context used downstream by graphics/media patches. - bootstrap_hide.js - initializes the internal bootstrap context and moves startup values out of the public window surface into private pipeline state. It creates and maintains
FernwehContext, transfers bootstrap data into internal state objects, hides service fields from enumeration, and removes temporary global values once the required owners and retention snapshots are ready. - set_log.js - JS-side logging/diagnostic emitter. Creates a JS-side logger/diag buffer and a unified
__DEGRADE__channel. - probe.js - pipeline observability and self-checking layer. It validates runtime invariants, descriptor integrity, call semantics, and timeout behavior after patches are loaded.
JavaScript (assets/scripts/window/patches)
- canvas.js - Canvas 2D/Offscreen hooks with seeded noise and invariant-preserving wrapping.
- webgl.js, WEBGL_DICKts.js -
WebGLinterception plus static whitelist/parameter support. - webgpu.js, WebgpuWL.js -
WebGPUinterception plus whitelist/limits data. - screen.js -
screenandvisualViewportsurface patching. - audiocontext.js - AudioContext-aligned seeded/media surface adjustments.
- font_module.js - consumes generated font configs, registers
@font-face, and injects CSS/font-loading glue. - rects.js is a window-realm module that influences DOM/SVG layout and rendering inputs, which are subsequently measured by native Chromium via and
Rect/SVG layout/geometry APIs. - RTCPeerConnection.js - ICE-server normalization and non-relay/network-shaping logic.
- nav_total_set.js, override_ua_data.js - navigator, UA-CH, language, and client-hint surface alignment on the window side.
- hide_webdriver.js - webdriver masking and related native-surface hardening.
- headers_interceptor.js, headers_bridge.js - request/header shaping on the JS side, synchronized with the CDP/mitmproxy path.
- GeoOverride_source.js, TimezoneOverride_source.js - geo/timezone overrides.
JavaScript (assets/scripts/workerscope)
- wrk.js - worker-scope coordinator for environment propagation and patch installation across Dedicated/Shared/Service Workers.
- WORKER_PATCH_SRC.js - worker-side patch source bundle consumed by bootstrap/prelude stages.
- worker_bootstrap.js - early worker bootstrap glue that connects worker creation with the injected patch payload.
- sw_prelude.js - Service Worker prelude used to establish the environment before worker patch application.
- set_reflect.js - worker-side reflection/native-surface helper.
- fonts-manifest.json - diagnostic manifest (large JSON).
- font_patch.generated.js - auto-generated fonts patch, consumed by font_module.js.
- font_patch.template.j2 - Jinja2 template used by rand_met.py to generate the JS patch.
Ready-to-use generated fonts are already included in assets/generated_fonts/Win32 and assets/generated_fonts/MacIntel, together with their generated font indexes. For a normal run, you do not need to add or move any font files.
The bundled fonts were taken from Google Fonts and then renamed by the project pipeline for runtime use. They are meant to make the project start without forcing every user to search for their own font set first.
If you want to use your own fonts, place only .woff2 files into assets/fonts_raw and run the project normally. During startup, rand_met.py treats assets/fonts_raw as an intake folder: it validates the files, skips unsuitable fonts, copies accepted files into the platform-specific generated folder, updates the index, and regenerates the runtime font patch. After a file is accepted, it is removed from that intake folder.
Custom fonts should be normal text fonts. Avoid icon fonts, emoji fonts, empty/broken files, fonts without basic ASCII coverage, and fonts with restrictive embedding flags or extreme metrics.
-
type in console to get:
-
L.PROBE(); probe.js log console output, json and html files saving;
-
L.DEGRADE.getBuffer(); - set_log.js console output;
-
set_log.js json log file saving:
(() => { const json = JSON.stringify(L.__DEGRADE__?.getBuffer?.() || [], null, 2); const stamp = new Date().toISOString().replace(/[:.]/g, "-"); const url = URL.createObjectURL(new Blob([json], { type: "application/json" })); const a = Object.assign(document.createElement("a"), { href: url, download: `degrade-buffer-${stamp}.json` }); a.click(); URL.revokeObjectURL(url); })();
You also can easily just type L.exportMyDebugLog() in Devtools console to get the same result.
Open main.py, choose one active profile, then press Play/F5 in VS Code.
With mitmproxy:
MITMPROXY_ON = True
# MITMPROXY_OFF = TrueWithout mitmproxy:
# MITMPROXY_ON = True
MITMPROXY_OFF = TrueInstall deps:
python -m venv venv
venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txtPut .ovpn files into cfg_vpn.
Fonts are already prepared in assets/generated_fonts; no font setup is required for a normal run. To add your own fonts, put .woff2 files into assets/fonts_raw and let the startup font pipeline process them.
Run main.py from VS Code or from the terminal. The active mitmproxy profile is selected inside main.py by the switch shown above.
- If you face error "permission denied" during installation → run
pip install --no-cache-dir -r requirements.txt
- Implement TLS fingerprint rotation via OpenSSL.
- Treat
success/readyevents from places that only record hook installation only asapplied: the mechanism is installed, but the result is not yet proven. - For the font module, emit
successonly if the state is observable after the current chain through theDOM/CSS/font-measurement surface, not only through internal structures; otherwise markapplied_but_not_effectiveor emit nosuccess.