Found in review of the previous commits:
- identity_seed() hashed only the UA, platform, screen size and core count.
Those take a handful of values per OS, so over 500 launches the seed took
12-30 distinct values and every install drew its fonts, voices, GPU, media
devices and canvas/audio noise seeds from that same short list. The seed now
mixes in identity_salt(): derived from what the caller pinned the identity
with (a Fingerprint, a preset dict, a config naming the UA) so relaunching
that identity reproduces every draw, and random otherwise. A pinned preset
now reproduces its noise seeds too; seeds the caller sets are kept.
- Concurrent AsyncNewBrowser launches on one driver interleaved pin/restore:
one browser inherited the other's mask and the driver could stay pinned.
pin -> launch -> restore is serialized per driver.
- Every pinned browser landed on cores 0..N-1; pins now take N adjacent cores
from a random start.
- A pinnable host with 1-3 cores reported 1, 2 or 3 (2 is the
resistFingerprinting value); the table floor of 4 applies as on other hosts.
- launch_options() callers that launch the browser themselves (launch_server,
direct use) kept the drawn core count although nothing pins the browser;
only Camoufox/AsyncCamoufox pass pin_cpu_cores=True now, everyone else
reports the host's snapped count.
- PLAUSIBLE_CORE_COUNTS gains 18, 22, 28 and 32, all recorded in the -v150
corpus.
- The Windows voice list was drawn before the locale was resolved, so an
fr-FR identity got en-US voices; it is drawn after locale/geoip now.
- macOS "Alex" gets its com.apple.speech.synthesis.voice identifier.
- CAMOU_PREFS env chunks are ASCII-only JSON (Windows getenv goes through the
ANSI code page).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
get_screen_cons() bounds the generated fingerprint to the monitor, but
BrowserForge honours a Screen constraint only when its pool has a match:
FingerprintGenerator.partial_csp catches the filtering failure and deletes the
constraint unless strict=True. So a 1366x768 laptop routinely gets a 2560x1440
fingerprint with window.outerWidth 1920, and browser-init resizes the real
chrome window to it -- rendering past the edge of the monitor.
Re-apply the bound after generation instead of trusting BrowserForge with it,
and pull screenX/screenY back inside the shrunken screen.
Headful only. headless has no window to overflow, and headless='virtual' runs a
1x1 Xvfb whose "monitor" would otherwise shrink the fingerprint to 1x1.
Fixes#499
Fixes the new_page() hang from #666, and restores the pythonlib/ + settings/
halves of #637-#647 that were dropped when those PRs were consolidated into #666
(that PR only carried patches/ + additions/, so these never actually landed).
## new_page() hangs when window.outer* is spoofed (#666)
The outer-size hijack in browser-init.patch pinned the chrome documentElement to
the spoofed size. That caps .browserStack, which caps the content viewport, so
the content window can never reach the size Juggler asks for in
updateViewportSize() -- and awaitViewportDimensions awaits exact equality with
no timeout, so it deadlocks rather than erroring. The second new_page() hung
forever and took the context with it.
The pin was never load-bearing: GetOuterWidth/GetOuterHeight already consult
MaskConfig unconditionally (fingerprint-injection.patch), so window.outerWidth is
spoofed in C++ regardless of the real chrome window size. Resizing is enough.
Measured on the official v152.0.4-beta.26 build (headless):
config before after
none pass pass
inner pass pass
outer HANG pass
both HANG pass (iw:360 ih:740 ow:360 oh:800 -- exact)
This corrects the diagnosis in #666, which blamed the inner+outer combination and
the `!(outerWidth || outerHeight)` guard. outer* ALONE is sufficient to hang, and
dropping inner* does not help, so that guard is not the culprit.
Also fixed driver-side: Playwright's implicit 1280x720 viewport is what asks for
the impossible size, so the driver now defaults to no_viewport when the config
spoofs any window dimension. That fixes the hang on already-released builds
without a rebuild. An explicit viewport=/no_viewport= from the caller wins.
## WebRTC ICE prefs (#538)
#666 merged the C++ half of the WebRTC fix but not the prefs, so the shipped
build still has no_host=true and none of the proxy_only prefs.
proxy_only_if_behind_proxy is the pref that actually stops the real-IP leak: it
prevents a UDP STUN request routing around a TCP proxy. no_host=false keeps the
stock two-candidate shape, which obfuscate_host_addresses makes leak-free.
## Also restored from the consolidation
- fix(proxy): dom.security.https_first rewrote http:// before the launch-arg
proxy filter saw it, breaking CONNECT-only proxies (#638).
- fix(stealth): speech-voice spoofing + stop leaking host voices (#646).
- fix(stealth): clamp inner <= outer <= avail <= screen; BrowserForge can emit
impossible geometries that leak as tells (#647).
Refs: https://github.com/daijro/camoufox/pull/666
Refs: https://github.com/daijro/camoufox/issues/538