Commit Graph
4 Commits
Author SHA1 Message Date
Jake WriterandClaude Opus 5 072ad02797 fix(pythonlib): per-identity salt for seeded draws; core pinning under concurrency
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>
2026-09-16 22:17:40 -06:00
Jake WriterandClaude Opus 5 3f0f850bf3 fix(pythonlib): identity draws that match real machines and stay stable
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>
2026-09-16 22:09:40 -06:00
Pratyush Sharma 2266f27501 Clamp headful window geometry to the real display
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
2026-07-28 00:29:03 -06:00
Jake Writer 0e4151f820 fix: page-recycle hang under spoofed window dims, and the unmerged halves of #637-#647
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
2026-07-16 14:38:39 -06:00