Launcher-side fixes found by comparing camoufox against stock Firefox 152.0.4
on Linux, Windows 11 and macOS hosts:
- DNT / GPC: BrowserForge draws doNotTrack "1" on most Firefox samples, but a
stock Firefox 152 reports "unspecified" and globalPrivacyControl false; the
stock defaults are used unless the caller sets them, and both are applied as
prefs so the API, the worker and the DNT / Sec-GPC headers agree (#760).
- Timezone and geolocation: the timezone is passed to the browser, and a
configured position sets permissions.default.geo so permissions.query agrees
with the auto-grant (#769, #773).
- hardwareConcurrency: the reported count is the fingerprint's and the browser
is pinned to that many cores (cpu_affinity.py, Linux/Windows), so worker
timing agrees with it; otherwise the host count snapped into the core counts
real machines ship with (never 2, Firefox's resistFingerprinting value).
- Fonts: the OS base is always present in full, OS-version variants are drawn
all-or-nothing, co-shipped groups stay together, Cascadia is never claimed
off Windows, a native macOS/Windows identity claims only the real OS base,
and gfx.font_rendering.fallback.async is off on Linux so per-character
fallback does not depend on cmap-load timing.
- Speech voices: a per-OS installed-voice model (voice-manifests.json) with
the voiceURI formats each backend really produces (voice-uris.json); no
default voice where stock has none.
- WebGL: extensions a release Firefox never exposes are filtered, but
OVR_multiview2 stays for Windows D3D11 renderers, which expose it.
- Media devices: a seeded draw of common per-OS devices with OS-style labels.
- Windows scrollbars follow the drawn Windows version (overlay on 11).
- Glyph-advance perturbation (fonts:spacing_seed) defaults to off: it moved
every measureText width off the value the same font gives on a real machine.
- Launcher prefs are also exported as CAMOU_PREFS_1..N so camoufox.cfg applies
them at startup, and the browser UI locale follows the spoofed locale.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BrowserForge picks navigator/screen; the GPU is drawn separately from
webgl_data.db weighted only by OS. Nothing ties the two together, so the
synthetic path emits pairs no real machine ships -- a discrete desktop GPU
behind a 1024x600 panel. Consistency checks (Pixelscan, Fingerprint.com) read
that as masking even though every individual value is plausible on its own.
Builds on @dyiapanis's #730, which identified the problem and the GPU-class
thresholds, with three changes:
* Constrain the GPU to the screen rather than the screen to the GPU.
sample_webgl_for_screen does rejection sampling, so the GPU keeps
webgl_data.db's real OS-weighted distribution and the geometry -- already
reconciled against the real display and the window box by
clamp_screen_to_display / fix_screen_no_taskbar / clamp_window_dimensions
/ clamp_window_position -- is left alone.
* Where no coherent GPU exists at all (BrowserForge still carries
netbook-era geometry, and nothing in the pool drives a sub-1366x768
panel), raise_screen_to_gpu_floor lifts the screen instead. It measures
the screen-to-avail gap BEFORE mutating -- #730 computed it after
overwriting screen.height, which turned a 1024x600 -> 1080 bump into a
520px "taskbar", a fresh impossible-geometry tell -- and it runs BEFORE
clamp_screen_to_display so a genuinely small monitor still wins and a
headful window cannot be pushed back off its own display (#499).
* No Apple-M Retina floor. Apple silicon also ships in the Mac mini and Mac
Studio, which drive whatever external monitor is attached, so pinning it
to 2560x1600 would reject real hardware and shrink the pool for nothing.
Measured over 300 synthetic fingerprints, incoherent GPU/screen pairs fall
from 54.3% to 0%, with avail <= screen and availHeight < height holding in
every trial. The screen floor is a no-op for the Linux and Windows pools
(0/400 draws below it) and fires on 3.5% of macOS draws, so the entropy cost
is confined to the implausible tail it exists to remove.
Co-authored-by: D Yiapanis <d@yiapanis.co>