Open Bug 2043745 Opened 4 months ago Updated 4 months ago

Address Windows voice-list fingerprint surface

Categories

(Core :: Privacy: Anti-Tracking, enhancement)

enhancement

Tracking

()

People

(Reporter: tjr, Unassigned)

References

(Blocks 1 open bug)

Details

On Windows, speechSynthesis.getVoices() exposes the user's SAPI voice list to web content. The list is determined by the user's OS system locale plus any additional Windows language packs they have installed.

The vast majority of users (~73%) sit at their locale's base voice set. About 20% have added exactly one additional locale pack (overwhelmingly en-US). Larger pack installations are vanishingly rare (~1.5% combined for 3+ packs).

The recommended mitigation is the same cross-platform SpeechSynthesisPrimaryLocale RFP target proposed for macOS: filter the SAPI voice list to voices whose language tag matches the user's primary locale at the registration site in dom/media/webspeech/synth/windows/SapiService.cpp. This collapses the 26,649 cohorts to approximately 40-60 (one per major locale's base voice set), removing roughly 8 bits of fingerprint surface.

Current behavior

Per-locale base voice counts (top 25 by user population)

Locale Dominant % at % above % below
en-US 5 78.86% 18.84% 2.30%
de-DE 5 76.82% 22.63% 0.55%
fr-FR 5 77.59% 21.64% 0.77%
pt-BR 4 85.93% 13.22% 0.85%
zh-Hans-CN 5 83.17% 15.59% 1.24%
pl-PL 4 77.54% 21.86% 0.60%
es-ES 5 77.95% 20.06% 1.99%
ru-RU 4 56.98% 38.85% 4.16%
en-GB 5 61.29% 36.90% 1.82%
it-IT 4 76.64% 22.84% 0.52%
en-IN 7 44.17% 18.79% 37.04%
es-MX 4 67.58% 31.39% 1.04%
en-ID 5 81.95% 17.65% 0.40%
ja-JP 6 80.13% 19.50% 0.36%
en-CA 7 50.79% 24.48% 24.72%
cs-CZ 2 62.38% 36.86% 0.76%
hu-HU 2 80.71% 18.50% 0.79%
nl-NL 2 69.53% 28.90% 1.57%
en-AU 7 47.07% 23.56% 29.37%
tr-TR 2 83.26% 15.57% 1.16%
de-CH 7 45.55% 23.95% 30.49%
de-AT 6 48.70% 20.83% 30.47%
el-GR 6 50.23% 8.49% 41.28%
fi-FI 2 68.76% 30.34% 0.90%
pt-PT 2 47.62% 51.45% 0.92%

The observed dominant values across the 80 qualifying locales (≥5000 users) are {1, 2, 3, 4, 5, 6, 7, 8}. The "% at" column reads as cohesion of that locale's modal install — locales with high values are homogeneous (a single Windows install gives the same voice set to nearly everyone), and low values indicate fractured install patterns.

What stands out:

  • Multi-locale "English overseas" regions cluster at dominant=7 (en-IN, en-CA, en-AU, de-CH, fr-CH). The 7-voice modal install is consistent with en-US base (5) + regional pack (2). These locales also have very high "% below" (24-37%), meaning a meaningful fraction of users stay on the en-US base alone without the regional pack.
  • de-AT and el-GR have dominant=6 but ~30-41% below, suggesting Austria and Greece have two visible install patterns — the base locale install and the base-plus-pack install — neither of which dominates strongly.
  • Locales with unusually high "% above" (ru-RU 38.85%, en-GB 36.90%, cs-CZ 36.86%) indicate places where adding a second pack is more common than typical. Notably these span different dominants (4, 5, 2), so it's not a "size of base voice set" effect but a behavioral one.
  • The "% below" column reveals a previously-invisible cohort: 4.83% of Windows users overall have fewer voices than their locale's modal install. The cause is not investigated in this data.

Estimated total packs installed

Decomposing each user's voices_count as (own locale dominant) + (sum of one or more other-locale dominants), using greedy match against the universe of observed dominants and falling back to "subtract 5" (the most-common dominant) when no exact match exists. Each subtraction counts as one additional pack; the "1" row below means the user is at their own locale's dominant (no additional packs).

Packs (own + added) % of Windows
1 (at locale dominant — no extras) 73.22%
2 (own + 1 added, usually en-US) 20.46%
3 (own + 2 added) 1.01%
4 0.17%
5 0.10%
6 0.07%
7 0.04%
8+ 0.11%
Below own dominant 4.83%

The fingerprint signal is overwhelmingly carried by the ~20% two-pack cohort. Single-pack users converge on their locale's modal voice set, so they contribute relatively little entropy within their locale (the per-locale "% at" rows are tight). Three-or-more-pack users are a long tail under 1.5% combined — locally identifying for those users.

Recommended fix

Filter voices to an allowlist, possibly based on locale. The proposed RFP target follows the tiered design from the macOS entropy bug:

  • DefaultVoicesOnly (more aggressive): replace the SAPI voice list with a single canonical default voice for the user's locale.
  • DefaultPlusPrimaryLangpack (default-FPP candidate): keep all SAPI voices whose language tag matches the primary locale, drop the rest.

The second variant aligns with how the Windows base install already partitions users, and produces ~40-60 cohorts globally (one per major locale's base set).

FPJSv2 uniqueness on Windows (simulated)

Phase pct_unique
Baseline (no protections) 82.87%
Phase 0-3 (all existing protections) 34.90%
+ Windows Voices Localized 30.83%
+ Windows Voices Aggressive 28.89%

This post might be better under a dedicated RFP WebSpeech issue, or we could do it in tandem with FPP patches - feel free to move it there

The list is determined by the user's OS system locale plus any additional Windows language packs they have installed

I know this is FPP, but (and you could use these suggestions in FPP)

For RFP I would like to see: re-framing the above (it may need to be different depending on the platform)

  • only return the default speech engine names as per locale (so spoof_english would return the en-US ones)
    • this would require maintaining a list of engines per locale (en, de, fr... etc)
      • ^ or we could maintain them for the major locales and default to primary for the others?
    • we would want to maintain a en-US spoof list at a minimum
  • if the platform lacks the corresponding speech engine map the spoofed name (revealed to web content) to the real primary/default one (the name is not exposed to web content)
    • e.g. FR but spoof_english but lacks english speech engines, so map it to e.g. Microsoft Hortense - French (France)

This would make speech engines equivalency of locale - but IDK how hard it is to cover/code the issues of

  • user tries to use a speech engine but has none because we said he does
  • user tries to use e.g. Microsoft David (en) but it has to use Microsoft Julie (fr)

I also don't consider fingerprinting of the actual voice (e.g. timing) an issue at this stage

FWIW this is what I have (I ended up with JA/FR stuff when testing stuff, like keyboards and font substitutions)

  • I don't know what added the Desktop variants - I think they turned up in a recent'ish win11 update
[
  "Microsoft David - English (United States) | en-US",
  "Microsoft Mark - English (United States) | en-US",
  "Microsoft Zira - English (United States) | en-US",
  "Microsoft Hortense - French (France) | fr-FR",
  "Microsoft Julie - French (France) | fr-FR",
  "Microsoft Paul - French (France) | fr-FR",
  "Microsoft Ayumi - Japanese (Japan) | ja-JP",
  "Microsoft Haruka - Japanese (Japan) | ja-JP",
  "Microsoft Ichiro - Japanese (Japan) | ja-JP",
  "Microsoft Sayaka - Japanese (Japan) | ja-JP",
  "Microsoft David Desktop - English (United States) | en-US",
  "Microsoft Zira Desktop - English (United States) | en-US",
  "Microsoft Hortense Desktop - French | fr-FR",
  "Microsoft Haruka Desktop - Japanese | ja-JP"
]
You need to log in before you can comment on or make changes to this bug.