speechSynthesis.getVoices() раскрывает список установленных голосов синтеза речи без запроса разрешения — стабильный сигнал, который редко блокируется.
Каждая операционная система поставляется с набором встроенных голосов для синтеза речи, и каждый браузер раскрывает полный список любой странице, которая его запросит — без клика, без диалога с разрешением, без запроса в стиле камеры. window.speechSynthesis.getVoices() был создан для того, чтобы программы чтения с экрана и функции озвучивания текста могли выбрать голос, но возвращаемый им список одновременно служит тихим и весьма эффективным сигналом для фингерпринтинга: он одновременно отражает вашу операционную систему, языковые пакеты и производителя браузера — и не меняется от очистки куки.
Ключевые выводы
speechSynthesis.getVoices()не требует разрешения и возвращает полный список установленных голосов синтеза речи любому вызвавшему её скрипту.- Каждый голос раскрывает поля
name,lang,localServiceиdefault— а набор, порядок и количество голосов зависят от операционной системы, установленных языковых пакетов и производителя браузера. - Список часто загружается асинхронно: первый вызов может вернуть пустой массив, и скрипту приходится ждать события
voiceschanged, чтобы увидеть реальный набор — причём это поведение само по себе различается между браузерами. - Список голосов можно сверить с заявленными ОС и браузером, точно так же как строки GPU — если User-Agent заявляет «macOS Safari», а список голосов доступен только для Windows, это несоответствие стоит отметить.
- Сигнал стабилен между сессиями и не зависит от очистки куки или приватного просмотра, поскольку исходит из установленного ПО, а не из хранимых данных.
Что такое фингерпринтинг синтеза речи
Фингерпринтинг синтеза речи считывает список голосов синтеза речи, доступный браузеру через интерфейс синтеза Web Speech API — семейство API, которое также лежит в основе программ чтения с экрана и расширений для озвучивания страниц. Сам вызов, SpeechSynthesis.getVoices(), синхронный, не требует разрешения и возвращает массив объектов SpeechSynthesisVoice, описывающих каждый голос, который браузер может передать в speak().
Во время сбора ничего не воспроизводится и не создаётся никакого аудио — это чистое чтение инвентаря, по сути ближе к перечислению шрифтов или перечислению медиаустройств, чем к аудио- или Canvas-фингерпринтингу, которые измеряют, как что-то рендерится. Здесь важно лишь то, какие именованные необязательные ресурсы установлены — а этот список достаточно различается между машинами, чтобы быть полезным сам по себе, и ещё полезнее в сочетании с другими сигналами.
Что раскрывает каждый голос
Каждый элемент массива — это SpeechSynthesisVoice с четырьмя читаемыми полями:
name— человекочитаемая метка, например «Microsoft David - English (United States)» или «Samantha»lang— языковой тег BCP 47, напримерen-USилиru-RUlocalService—true, если голос обрабатывается на устройстве,false, если зависит от удалённого сетевого сервиса (характерно для некоторых встроенных голосов Google в Chrome)default—trueдля голоса, который браузер выбрал бы, если не указан конкретный
function getVoiceFingerprint() {
const voices = speechSynthesis.getVoices();
return voices
.map(v => `${v.name}|${v.lang}|${v.localService}`)
.sort()
.join(';');
}
Скрипту вообще не нужно вызывать speak() — чтения массива и хеширования отсортированных троек name/lang/localService достаточно, чтобы получить стабильный идентификатор для голосового инвентаря конкретной машины.
Ловушка асинхронной загрузки
Единственная сложность, которую приходится обходить трекерам (и разработчикам) — это тайминг. В некоторых браузерах вызов getVoices() сразу после загрузки страницы возвращает пустой массив, потому что браузер ещё не успел запросить у операционной системы установленные голоса. Задокументированное на MDN решение — подписаться на событие voiceschanged и повторно прочитать список после его срабатывания:
function collectVoicesReliably() {
return new Promise((resolve) => {
let voices = speechSynthesis.getVoices();
if (voices.length) return resolve(voices);
speechSynthesis.onvoiceschanged = () => {
resolve(speechSynthesis.getVoices());
};
});
}
Эта особенность — не просто неудобство для разработчиков, она сама является сигналом фингерпринтинга. То, нужен ли браузеру обходной путь через voiceschanged, и сколько времени занимает заполнение списка, различается по движку браузера и платформе, добавляя ещё одно небольшое измерение сверх самого списка голосов.
Насколько это уникально и стабильно
Голосовые инвентари меняются по трём независимым осям, и каждая добавляет разделение между профилями:
| Ось | Почему различается |
|---|---|
| Операционная система | Windows, macOS и Linux поставляются с разными наборами голосов по умолчанию под разными именами |
| Установленные языковые пакеты | Машина, настроенная на несколько языков, раскрывает больше голосов, чем установка с одним языком по умолчанию |
| Производитель браузера | Некоторые браузеры добавляют собственные встроенные или сетевые голоса (localService: false) поверх списка ОС; другие раскрывают только то, что предоставляет ОС |
Поскольку список отражает реально установленное ПО, а не особенность рендеринга, он ведёт себя не столько как случайный источник шума для каждого устройства, сколько как структурированный инвентарь — ближе по духу к вопросу «какие шрифты установлены», чем к «как этот GPU округляет числа с плавающей точкой». Установка с одним языком по умолчанию даёт короткий, общий для многих похожих машин список; настройка многоязычного продвинутого пользователя с несколькими установленными голосовыми пакетами выделяется на этом фоне. В сочетании с сигналами вроде WebGL и установленных шрифтов это добавляет ещё одну практически независимую ось к общему отпечатку, следуя той же комбинаторной логике, что описана в нашем руководстве по фингерпринтингу браузера: ни один сигнал сам по себе не решает всё, но независимые сигналы перемножаются.
Это также полезная проверка на согласованность. Список голосов, целиком состоящий из имён в стиле Windows SAPI, при User-Agent, заявляющем macOS — это как раз та разновидность противоречия, которая выдаёт подделанный или неверно настроенный профиль, из той же категории, что несоответствия GPU/ОС из статьи Несоответствие оборудования в фингерпринте — список голосов должен согласовываться со всем остальным, что браузер сообщает о себе.
С точки зрения стабильности список голосов на конкретной машине меняется редко. Он сдвигается только при обновлении встроенных голосов операционной системой, установке или удалении языкового пакета пользователем, либо при крупном обновлении браузера, добавляющем новые встроенные голоса — в остальном это устойчивый межсессионный идентификатор, который не сбрасывается очисткой куки, приватным окном или сбросом кэша.
Способы защиты
- Настройки браузера, ориентированные на приватность. Браузеры и режимы, построенные вокруг унификации — настройки защиты от фингерпринтинга Firefox и Tor Browser — стремятся нормализовать или ограничить такие в высокой степени специфичные для устройства поверхности API, ценой нестандартного поведения на сайтах, ожидающих полный список голосов.
- Понимайте ограничения приватного просмотра. Как и с аудио- и Canvas-фингерпринтингом, приватный режим не меняет установленное ПО, а значит не меняет и ваш список голосов.
- Расширения, блокирующие или подменяющие Web Speech API, могут сгладить сигнал, но стоит проверить, что они не просто возвращают пустой массив таким образом, который сам по себе необычен и потому узнаваем — парадокс анти-фингерпринтинга из нашего руководства по фингерпринтингу браузера применим и здесь.
Не существует настройки, которая сократила бы установленный у вас список голосов до «типового», не убрав при этом реально используемые вами языковые пакеты или не заставив вас перейти на режим браузера, построенный вокруг слияния с большой однородной толпой.
Как проверить свой сигнал
Увидеть собственный голосовой отпечаток можно без единой строчки кода:
- Запустите инструмент проверки фингерпринта. Проверка отпечатка от BrowserInsight показывает сигналы, которые раскрывает ваш браузер, наряду с Canvas, WebGL и аудио, а проект EFF Cover Your Tracks показывает, насколько редкой является ваша общая конфигурация среди недавних посетителей.
- Сравните браузеры на одной машине. Откройте демонстрацию
getVoices()на MDN в двух разных браузерах на одном компьютере — даже если голоса уровня ОС в основном совпадают, количество голосов и наличие сетевых (localService: false) записей обычно различаются. - Проверьте поддержку браузером, прежде чем на неё полагаться. Покрытие синтеза речи в Web Speech API широкое, но не универсальное в старых версиях браузеров — см. текущую поддержку на caniuse.
Часто задаваемые вопросы
Требует ли speechSynthesis.getVoices() запроса разрешения?
Нет. В отличие от доступа к камере или микрофону, чтение списка установленных голосов не требует пользовательского разрешения и не вызывает никакого диалога. Любой скрипт на любой странице может вызвать эту функцию и сразу прочитать результат (с учётом описанной выше особенности асинхронной загрузки).
Почему getVoices() иногда возвращает пустой массив?
Некоторые браузеры получают список голосов от операционной системы асинхронно после загрузки страницы, поэтому слишком ранний вызов ничего не возвращает. Стандартное решение — подписаться на событие voiceschanged и повторно прочитать список после его срабатывания, как показано в примере кода выше.
Может ли фингерпринтинг синтеза речи идентифицировать меня сам по себе?
Редко — распространённая комбинация одноязычной ОС и браузера даёт список голосов, общий для многих людей. Сигнал становится значимым в сочетании с другими, такими как шрифты, Canvas и WebGL, а также как проверка на согласованность с заявленной операционной системой.
Сбрасывает ли очистка куки или приватный режим мой голосовой отпечаток?
Нет. Список голосов отражает ПО, установленное на устройстве, а не хранимые данные браузера, поэтому очистка куки, приватное окно или смена профиля его не меняют.
Заключение
Фингерпринтинг синтеза речи превращает API программ чтения с экрана, созданный для доступности, в тихое, не требующее разрешения чтение списка установленных голосов операционной системы и браузера. Сам по себе он грубо разделяет браузеры по платформе и языковой настройке; в сочетании со шрифтами, Canvas и WebGL он добавляет ещё одну независимую ось к общему отпечатку — и ещё одну проверку на согласованность. Как и с большинством поверхностей фингерпринтинга, не существует единственного переключателя, который бы его полностью убрал — есть только режимы браузера и расширения, снижающие его заметность.
Рекомендуемые статьи:


