Address Windows voice-list fingerprint surface
Categories
(Core :: Privacy: Anti-Tracking, 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% |
Comment 1•4 months ago
|
||
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)
- RFPTarget
SpeechSynthesis - currently RFP returns
none(Bug 1333641) - see https://gitlab.torproject.org/tpo/applications/tor-browser/-/work_items/41127#note_2873810
-
Can we implement something similar to what we did with
navigator.mediaDevices.enumerateDevices()... i.e. only return a single generic default voice (that maps to the default OS voice). And if there isn't one, still return that. That would prevent enumeration entropy.
-
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
- this would require maintaining a list of engines per locale (en, de, fr... etc)
- 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)
- e.g. FR but spoof_english but lacks english speech engines, so map it to e.g.
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 useMicrosoft 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
Desktopvariants - 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"
]
Description
•