fix: remove the glyph-spacing seed from the browser and the launcher

anti-font-fingerprinting.patch added a seeded amount to every glyph advance,
so that text widths differed per context. No real machine produces those
widths: the same font on the same OS measures the same everywhere. So the
noise was itself a fingerprint, measured in #779 at +1 px per ~100 glyphs
plus fractional deltas on every measureText. #779 defaulted the seed to 0
and kept it as an opt-in, but an opt-in whose only effect is to become
detectable is not worth carrying. Removed:

- The browser side:
  - FontSpacingSeedManager and window.setFontSpacingSeed;
  - the HarfBuzz hook;
  - the plumbing that existed only to carry the context id down to the
    shaper: the userContextId on gfxTextRun, gfxShapedWord and the word-cache
    key, and the extra MakeTextRun argument in nsTextFrame, nsFontMetrics,
    MathML and canvas.
  The font group keeps its userContextId, which font-list-spoofing.patch
  uses to apply the per-context font list. Text is now shaped exactly as
  stock Firefox shapes it.
- The fonts:spacing_seed key. The launcher had been sending 0 on every
  launch, plus a setFontSpacingSeed(0) call in every context's init script.
- tests/patches/config-overrides.py, which tested only the spacing override.
  A pythonlib test now covers config_overrides with another key.

timezone-spoofing, webrtc-ip-spoofing and window-setter-seal change only in
context lines and the setter seal list. Every patch applies cleanly to a
fresh tree, and the result builds. The settled decision is recorded as
no-glyph-spacing-noise, with an automated check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jake Writer
2026-09-25 20:20:15 -06:00
co-authored by Claude Opus 5.5
parent 86a20c73b7
commit 676fb3f7c1
20 changed files with 113 additions and 1249 deletions
+1 -8
View File
@@ -1645,9 +1645,6 @@ def from_preset(preset: Dict, ff_version: Optional[str] = None, salt: Optional[i
config['webGl:renderer'] = webgl['unmaskedRenderer']
# Generate a unique audio seed per launch (1 to 2^32-1, excluding 0 which is a no-op in C++)
# fonts:spacing_seed stays 0 (off): glyph-advance perturbation produces text
# widths no real machine emits (see launch_options in utils.py).
config['fonts:spacing_seed'] = 0
config['audio:seed'] = randint(1, 4_294_967_295) # nosec
if preset.get('timezone'):
@@ -1697,7 +1694,6 @@ def _build_init_script(values: Dict[str, Any]) -> str:
lines = ['(function(v) {', ' var w = window;']
setters = [
('fontSpacingSeed', 'setFontSpacingSeed', '{val}'),
('audioFingerprintSeed', 'setAudioFingerprintSeed', '{val}'),
('navigatorPlatform', 'setNavigatorPlatform', '{val}'),
('navigatorOscpu', 'setNavigatorOscpu', '{val}'),
@@ -1798,8 +1794,7 @@ def generate_context_fingerprint(
normalize_locale() and injected into config. Also sets
context_options['locale'] for Playwright.
config_overrides: Dict of CAMOU_CONFIG keys to override after config
is built but before init_script is rendered. Useful for disabling
perturbation (e.g. {'fonts:spacing_seed': 0}).
is built but before init_script is rendered (e.g. {'audio:seed': 7}).
"""
if preset is not None:
# Use real fingerprint preset
@@ -1816,7 +1811,6 @@ def generate_context_fingerprint(
_salt = identity_salt()
# Add seeds (the generator doesn't produce these)
config.setdefault('fonts:spacing_seed', 0) # perturbation off; see utils.launch_options
config.setdefault('audio:seed', randint(1, 4_294_967_295)) # nosec
# Determine target OS from platform for font/voice generation
@@ -1912,7 +1906,6 @@ def generate_context_fingerprint(
# Build the values dict for the init script (works for both paths)
init_values: Dict[str, Any] = {
'fontSpacingSeed': config.get('fonts:spacing_seed'),
'audioFingerprintSeed': config.get('audio:seed'),
'navigatorPlatform': nav.get('platform'),
'navigatorOscpu': config.get('navigator.oscpu'),
+2 -8
View File
@@ -1267,18 +1267,12 @@ def launch_options(
if not _user_set_accept_encoding:
config.pop('headers.Accept-Encoding', None)
# Set random seeds for fingerprint noise (per launch)
# Glyph-advance perturbation is OFF by default (seed 0): it moves every
# measured text width off the value the same font produces on a real
# machine (measured 2026-09-14: +1 px per ~100 glyphs, fractional deltas
# on every measureText), which is a fingerprint no stock Firefox emits.
# Pass fonts:spacing_seed explicitly to opt back in.
set_into(config, 'fonts:spacing_seed', 0)
# The audio noise seed follows the identity: a returning "same device" must
# reproduce its audio hash (#442/#765). Never 0 (0 disables the noise). A
# preset draws its own random seed; it is replaced here too so a pinned
# preset reproduces it, but a seed the caller set is kept. There is no
# canvas seed: the browser adds no canvas noise (#528).
# canvas seed: the browser adds no canvas noise (#528), and no glyph-spacing
# noise either (ci/tribal-rules.yml: no-glyph-spacing-noise).
if not _user_set_audio_seed:
_ident = identity_seed(config, _identity_salt)
config['audio:seed'] = ((_ident * 2654435761 + 97) & 0xFFFFFFFF) or 1
+16
View File
@@ -178,3 +178,19 @@ def test_fingerprint_preset_off_never_draws_a_preset(off):
checked with `is not None`, so False drew a random bundled preset."""
with mock.patch.object(utils, "get_random_preset", side_effect=AssertionError("preset drawn")):
launch(fingerprint_preset=off)
def test_no_glyph_spacing_seed_is_generated():
"""Glyph-spacing noise moved every measured text width off what the same
font gives on a real machine, so it was itself a fingerprint; the feature
is gone from the browser, and the launcher sends nothing for it."""
assert "fonts:spacing_seed" not in launch()
context = fp.generate_context_fingerprint(os="linux")
assert "fonts:spacing_seed" not in context["config"]
assert "setFontSpacingSeed" not in context["init_script"]
def test_config_overrides_reach_the_config_and_the_init_script():
context = fp.generate_context_fingerprint(os="linux", config_overrides={"audio:seed": 7})
assert context["config"]["audio:seed"] == 7
assert "setAudioFingerprintSeed(7)" in context["init_script"]