mirror of
https://github.com/daijro/camoufox.git
synced 2026-10-05 08:00:21 +00:00
fix(python): give NewContext identities the browser's Firefox version
NewContext() and AsyncNewContext() passed ff_version=None through to generate_context_fingerprint(), so a context's user agent kept the version fpgen drew (e.g. Firefox/146) while the browser underneath was 152. They now default ff_version to the major version of Playwright's Browser.version, which Juggler reports from MOZ_APP_VERSION_DISPLAY, so the UA always names the browser the page is actually talking to. An explicit ff_version still wins. The docstrings said each context gets "its own real fingerprint preset"; the default has been an fpgen draw, with a preset only when one is passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
165e68f831
commit
46e5c8d1a4
@@ -610,7 +610,7 @@ bundles are shipped in the wheel.
|
||||
|
||||
| Property | Source | Notes |
|
||||
|----------|--------|-------|
|
||||
| UA, platform, HWC, oscpu | fpgen or preset | UA version patched to match Camoufox Firefox version |
|
||||
| UA, platform, HWC, oscpu | fpgen or preset | UA version patched to the browser's Firefox version (NewContext reads it from Playwright's `browser.version` unless `ff_version` is given) |
|
||||
| Screen dims, colorDepth | fpgen or preset | Viewport adjusted by -28px for browser chrome |
|
||||
| WebGL vendor/renderer | `sample_webgl()` from `webgl_data.db` | OS-weighted probability sampling. fpgen's own WebGL fields are not mapped in `fpgen.yml` yet, so both paths call `sample_webgl()`. |
|
||||
| Font list | `_generate_random_font_subset()` | One weighted OS-version base in full, plus each addition unit at its measured probability; marker fonts always included. See [FONTS.md](FONTS.md). NOT from presets. |
|
||||
|
||||
@@ -195,15 +195,15 @@ async def AsyncNewContext(
|
||||
"""
|
||||
Creates a new browser context with a unique fingerprint identity.
|
||||
|
||||
Each context gets its own real fingerprint preset (navigator, screen, WebGL, fonts, etc.)
|
||||
with its own audio noise seed. All values are applied
|
||||
Each context gets its own identity (navigator, screen, WebGL, fonts, voices),
|
||||
drawn by fpgen unless a preset is given, with its own audio noise seed. All values are applied
|
||||
via addInitScript so they self-destruct before page scripts can detect them.
|
||||
|
||||
Parameters:
|
||||
browser: A Browser instance from AsyncNewBrowser or AsyncCamoufox.
|
||||
preset: A specific fingerprint preset dict to use. If None, picks randomly.
|
||||
os: Target OS for preset selection ("windows", "macos", "linux").
|
||||
ff_version: Firefox version string for UA patching.
|
||||
preset: A fingerprint preset dict to use. If None, fpgen draws a new identity.
|
||||
os: Target OS for the drawn identity ("windows", "macos", "linux").
|
||||
ff_version: Firefox major version to claim in the UA. Defaults to the browser's own.
|
||||
webrtc_ip: IPv4 address to spoof for WebRTC ICE candidates.
|
||||
proxy: Per-context proxy (Playwright format: {"server": "...", "username": "...", "password": "..."}).
|
||||
Unless webrtc_ip and timezone_id are both given, they are looked up from the
|
||||
@@ -211,6 +211,10 @@ async def AsyncNewContext(
|
||||
geolocation: Per-context geolocation ({"latitude": float, "longitude": float}).
|
||||
**context_kwargs: Additional Playwright new_context() options.
|
||||
"""
|
||||
# The drawn UA carries fpgen's Firefox version, which must not disagree with
|
||||
# the browser the page is actually talking to.
|
||||
ff_version = ff_version or browser.version.split('.', 1)[0]
|
||||
|
||||
# Auto-derive WebRTC IP and timezone from proxy's exit IP when not explicitly provided
|
||||
if proxy and (not webrtc_ip or "timezone_id" not in context_kwargs):
|
||||
exit_ip, timezone = await _resolve_proxy_geo(proxy)
|
||||
|
||||
@@ -173,15 +173,15 @@ def NewContext(
|
||||
"""
|
||||
Creates a new browser context with a unique fingerprint identity.
|
||||
|
||||
Each context gets its own real fingerprint preset
|
||||
with its own audio noise seed. All values are applied
|
||||
Each context gets its own identity (navigator, screen, WebGL, fonts, voices),
|
||||
drawn by fpgen unless a preset is given, with its own audio noise seed. All values are applied
|
||||
via addInitScript so they self-destruct before page scripts can detect them.
|
||||
|
||||
Parameters:
|
||||
browser: A Browser instance from NewBrowser or Camoufox.
|
||||
preset: A specific fingerprint preset dict to use. If None, picks randomly.
|
||||
os: Target OS for preset selection ("windows", "macos", "linux").
|
||||
ff_version: Firefox version string for UA patching.
|
||||
preset: A fingerprint preset dict to use. If None, fpgen draws a new identity.
|
||||
os: Target OS for the drawn identity ("windows", "macos", "linux").
|
||||
ff_version: Firefox major version to claim in the UA. Defaults to the browser's own.
|
||||
webrtc_ip: IPv4 address to spoof for WebRTC ICE candidates.
|
||||
proxy: Per-context proxy (Playwright format: {"server": "...", "username": "...", "password": "..."}).
|
||||
Unless webrtc_ip and timezone_id are both given, they are looked up from the
|
||||
@@ -189,6 +189,10 @@ def NewContext(
|
||||
geolocation: Per-context geolocation ({"latitude": float, "longitude": float}).
|
||||
**context_kwargs: Additional Playwright new_context() options.
|
||||
"""
|
||||
# The drawn UA carries fpgen's Firefox version, which must not disagree with
|
||||
# the browser the page is actually talking to.
|
||||
ff_version = ff_version or browser.version.split('.', 1)[0]
|
||||
|
||||
# Auto-derive WebRTC IP and timezone from proxy's exit IP when not explicitly provided
|
||||
if proxy and (not webrtc_ip or "timezone_id" not in context_kwargs):
|
||||
exit_ip, timezone = _resolve_proxy_geo(proxy)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"""A context's user agent carries the Firefox version of the browser it runs in.
|
||||
|
||||
Without an explicit ff_version, NewContext kept whatever version fpgen drew
|
||||
(e.g. Firefox/146) on a 160 browser, so the UA disagreed with every
|
||||
version-dependent API the page could probe.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from camoufox import async_api, sync_api
|
||||
|
||||
|
||||
def _user_agent(init_script):
|
||||
return re.search(r'setNavigatorUserAgent\("([^"]+)"\)', init_script).group(1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("api", ["sync", "async"])
|
||||
def test_user_agent_matches_the_browser_version(api):
|
||||
context = mock.MagicMock()
|
||||
browser = mock.MagicMock()
|
||||
# Playwright's Browser.version for Firefox: MOZ_APP_VERSION_DISPLAY.
|
||||
browser.version = "160.0.1"
|
||||
if api == "sync":
|
||||
browser.new_context.return_value = context
|
||||
sync_api.NewContext(browser, os="linux")
|
||||
else:
|
||||
context.add_init_script = mock.AsyncMock()
|
||||
browser.new_context = mock.AsyncMock(return_value=context)
|
||||
asyncio.run(async_api.AsyncNewContext(browser, os="linux"))
|
||||
|
||||
user_agent = _user_agent(context.add_init_script.call_args.args[0])
|
||||
assert "Firefox/160.0" in user_agent and "rv:160.0" in user_agent, user_agent
|
||||
@@ -34,6 +34,7 @@ EXIT = {"status": "success", "query": "203.0.113.7", "timezone": "Europe/Paris"}
|
||||
|
||||
def _sync_browser():
|
||||
browser = mock.MagicMock()
|
||||
browser.version = "152.0.4"
|
||||
return browser
|
||||
|
||||
|
||||
@@ -41,6 +42,7 @@ def _async_browser():
|
||||
context = mock.MagicMock()
|
||||
context.add_init_script = mock.AsyncMock()
|
||||
browser = mock.MagicMock()
|
||||
browser.version = "152.0.4"
|
||||
browser.new_context = mock.AsyncMock(return_value=context)
|
||||
return browser
|
||||
|
||||
|
||||
Reference in New Issue
Block a user