diff --git a/pythonlib/camoufox/fingerprints.py b/pythonlib/camoufox/fingerprints.py index cb18def..42c8ba1 100644 --- a/pythonlib/camoufox/fingerprints.py +++ b/pythonlib/camoufox/fingerprints.py @@ -1619,6 +1619,12 @@ def generate_context_fingerprint( # Fall back to synthetic generation fp = generate_fingerprint(os=os) config = from_fpgen(fp, ff_version) + # fpgen's Linux pool now and then pairs the Linux UA with a Windows + # platform and oscpu. launch_options() corrects that; this path did + # not, so ~1.6% of Linux contexts said Win32 -- and, since the OS below + # is read from the platform, drew Windows fonts and voices as well. + if 'Linux' in str(config.get('navigator.userAgent', '')): + fix_navigator_arch(config, 'lin') # A fresh identity: every seeded draw below gets its own salt. _salt = identity_salt() diff --git a/pythonlib/tests/test_identity_salt.py b/pythonlib/tests/test_identity_salt.py index 57fb5f2..7a386eb 100644 --- a/pythonlib/tests/test_identity_salt.py +++ b/pythonlib/tests/test_identity_salt.py @@ -199,3 +199,25 @@ def test_is_mobile_warns_that_camoufox_is_desktop_only(): with pytest.warns(LeakWarning, match="built for desktops"): launch(is_mobile=True, i_know_what_im_doing=False) + + +def test_a_context_with_a_linux_ua_is_linux_throughout(monkeypatch): + """fpgen's Linux pool now and then pairs the Linux UA with platform Win32 and + a Windows oscpu (~1.6% of NewContext Linux identities). launch_options() + fixed that and generate_context_fingerprint() did not -- and it reads the OS + for fonts and voices from the platform, so those came out Windows too.""" + real = fp.generate_fingerprint + + def mismatched(**kwargs): + drawn = real(**kwargs) + drawn["navigator"]["platform"] = "Win32" + drawn["navigator"]["oscpu"] = "Windows NT 10.0; Win64; x64" + return drawn + + monkeypatch.setattr(fp, "generate_fingerprint", mismatched) + config = fp.generate_context_fingerprint(os="linux")["config"] + + assert "Linux x86_64" in config["navigator.userAgent"] + assert config["navigator.platform"] == "Linux x86_64" + assert config["navigator.oscpu"] == "Linux x86_64" + assert "Segoe UI" not in config["fonts"] diff --git a/typescript/src/fingerprints.ts b/typescript/src/fingerprints.ts index a2fb4a0..897b505 100644 --- a/typescript/src/fingerprints.ts +++ b/typescript/src/fingerprints.ts @@ -1726,6 +1726,12 @@ export function generateContextFingerprint({ } else { const fp = generateFingerprint({ os: os ?? undefined }); config = fromFpgen(fp, ff_version); + // fpgen's Linux pool now and then pairs the Linux UA with a Windows + // platform and oscpu. launchOptions() corrects that; this path did not, + // so ~1.6% of Linux contexts said Win32 -- and, since the OS below is + // read from the platform, drew Windows fonts and voices as well. + if (pyStr(get(config, "navigator.userAgent", "")).includes("Linux")) + fixNavigatorArch(config, "lin"); // A fresh identity: every seeded draw below gets its own salt. const salt = identitySalt(); diff --git a/typescript/tests/fingerprints.test.ts b/typescript/tests/fingerprints.test.ts index 3970374..0763046 100644 --- a/typescript/tests/fingerprints.test.ts +++ b/typescript/tests/fingerprints.test.ts @@ -6,7 +6,7 @@ */ import * as fs from "node:fs"; import * as path from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { InvalidIP } from "../src/exceptions.js"; import { appVersionFromUserAgent, @@ -35,6 +35,7 @@ import { setMediaDevicesDefaults, WINDOWS_11_MARKER_FONTS, } from "../src/fingerprints.js"; +import { Generator } from "../src/fpgen/index.js"; import { LOCAL_DATA } from "../src/pkgman.js"; import { MODEL } from "./fpgen-setup.js"; @@ -887,4 +888,28 @@ describe.skipIf(!MODEL.ok)("fpgen generation (needs the model)", () => { expect(fromP.context_options.timezoneId).toBe("Europe/Paris"); expect(fromP.config["navigator.platform"]).toBe("MacIntel"); }); + + it("a context with a Linux UA is Linux throughout", () => { + // fpgen's Linux pool now and then pairs the Linux UA with platform Win32 + // and a Windows oscpu. launchOptions() fixed that and this path did not, + // and it reads the OS for fonts and voices from the platform. + const real = Generator.prototype.generate; + const spy = vi + .spyOn(Generator.prototype, "generate") + .mockImplementation(function (this: Generator, ...args: any[]) { + const drawn = (real as any).apply(this, args); + drawn.navigator.platform = "Win32"; + drawn.navigator.oscpu = "Windows NT 10.0; Win64; x64"; + return drawn; + }); + try { + const { config } = generateContextFingerprint({ os: "linux" }); + expect(config["navigator.userAgent"]).toContain("Linux x86_64"); + expect(config["navigator.platform"]).toBe("Linux x86_64"); + expect(config["navigator.oscpu"]).toBe("Linux x86_64"); + expect(config.fonts).not.toContain("Segoe UI"); + } finally { + spy.mockRestore(); + } + }); });