Implement deterministic, seed-based speech synthesis voice profile farbling that integrates with the existing setFingerprintingSeed() API.
Brave's existing farbling (speech_synthesis.cc):
- OFF: Returns real voice list
- BALANCED: Returns real voices + 1 fake voice (random name from 14 options)
- MAXIMUM: Returns empty voice list
Problem: Even in BALANCED mode, the fake voice name uses MakePseudoRandomGenerator() without domain-specific control, and we only add noise rather than providing full control.
Priority 1: Per-Context Custom Profiles (when setFingerprintingSeed is set)
↓ if HasMasterSeed() == true
Return deterministic voice list from predefined profiles
Priority 2: Brave's Existing Farbling (when farbling enabled, no seed)
↓ if farbling_level == BALANCED
Return real voices + 1 fake voice (current behavior)
Priority 3: No Farbling
↓ if farbling_level == OFF
Return real voice list unchanged
Create realistic voice profile sets for different "virtual platforms":
Profile Structure:
struct VoiceProfile {
const char* voice_uri;
const char* name;
const char* lang;
bool is_local_service;
bool is_default;
};Profile Sets (12 total, matching macOS voice patterns):
-
macOS Profile 1 (en-US dominant):
- Samantha (en-US, default)
- Alex (en-US)
- Daniel (en-GB)
- Karen (en-AU)
-
macOS Profile 2 (multilingual):
- Victoria (en-US, default)
- Thomas (fr-FR)
- Amelie (fr-CA)
- Joana (pt-PT)
-
macOS Profile 3 (minimal):
- Fred (en-US, default)
- Fiona (en-Scotland)
4-12. More variations with different combinations...
if (cache.HasMasterSeed()) {
// Use MakePseudoRandomGenerator to get deterministic seed-based selection
// This uses custom_farbling_token_ which is already domain-salted via HMAC-SHA256
brave::FarblingPRNG prng = cache.MakePseudoRandomGenerator(
brave::FarbleKey::kSpeechSynthesis);
// Select one of the 12 voice profiles deterministically
size_t profile_index = prng() % kVoiceProfiles.size();
const VoiceProfile* profile = kVoiceProfiles[profile_index];
// Build voice list from selected profile
voice_list_.clear();
for (const VoiceProfile& voice : profile) {
auto mojom_voice = mojom::blink::SpeechSynthesisVoice::New();
mojom_voice->voice_uri = blink::String(voice.voice_uri);
mojom_voice->name = blink::String(voice.name);
mojom_voice->lang = blink::String(voice.lang);
mojom_voice->is_local_service = voice.is_local_service;
mojom_voice->is_default = voice.is_default;
voice_list_.push_back(
MakeGarbageCollected<SpeechSynthesisVoice>(std::move(mojom_voice)));
}
VoicesDidChange();
return;
}File to Modify:
/Volumes/BuilderOSteroids/GitHub/brave-browser/src/brave/chromium_src/third_party/blink/renderer/modules/speech/speech_synthesis.cc
Pattern (same as screen farbling):
// PRIORITY 1: Custom profile system (when setFingerprintingSeed is called)
if (cache.HasMasterSeed()) {
// Use per-context voice profile
return CustomVoiceProfile();
}
// PRIORITY 2: Brave's existing farbling (when farbling enabled)
if (farbling_level == BraveFarblingLevel::BALANCED) {
// Use existing fake voice logic
return ExistingFakingVoice();
}
// PRIORITY 3: No farbling
OnSetVoiceList_ChromiumImpl(std::move(mojom_voices));- Deterministic: Same seed + same domain = same voice list
- Domain-salted: Different domains get different voice profiles (privacy)
- Subdomain consistency: www.example.com and api.example.com get same voices
- Realistic: Voice profiles match real macOS/Chrome installations
- Non-breaking: Existing Brave farbling still works as fallback
- Automation-friendly: Full control via setFingerprintingSeed()
- Cross-site tracking: Mitigated by HMAC-SHA256 domain salting
- Subdomain detection: Prevented by eTLD+1 normalization
- Profile database fingerprinting: All profiles are authentic macOS voices
- API detection: setFingerprintingSeed self-destructs (already implemented)
// Test 1: Determinism
window.setFingerprintingSeed(12345);
const voices1 = speechSynthesis.getVoices();
// reload page
const voices2 = speechSynthesis.getVoices();
assert.deepEqual(voices1, voices2); // ✅ Same
// Test 2: Subdomain consistency
// On www.example.com
window.setFingerprintingSeed(12345);
const voices_www = speechSynthesis.getVoices();
// On api.example.com
window.setFingerprintingSeed(12345);
const voices_api = speechSynthesis.getVoices();
assert.deepEqual(voices_www, voices_api); // ✅ Same eTLD+1
// Test 3: Cross-domain isolation
// On example.com
window.setFingerprintingSeed(12345);
const voices_ex = speechSynthesis.getVoices();
// On google.com
window.setFingerprintingSeed(12345);
const voices_goog = speechSynthesis.getVoices();
assert.notDeepEqual(voices_ex, voices_goog); // ✅ Different domains- ✅ Research existing implementation
- ✅ Design profile database and selection algorithm
- Implement custom voice profile system
- Update SPEECH.md documentation
- Test with automation frameworks