Вверху README у earshot, моего локального транскрибатора, висит зелёный значок “Network calls: none”. Ниже в README написано “No account, no key, no telemetry” и “After that you can run earshot with the network off”. Текст --help кончается фразой “Nothing leaves the machine. There is no network call anywhere in the pipeline.” Модель base.en из faster-whisper, 145 МБ, скачивается один раз.
Всё это я написал, исходя из замысла. Замером этого никто не проверял, я в том числе. Код транскрибации взят из приватного проекта, который я писал в конце мая и начале июня. В середине августа я урезал его до earshot и написал README. На этой неделе я поставил earshot с нуля в пустую папку, так, как README велит это делать незнакомому человеку. Потом стал следить за его сокетами.
Стенд
Ubuntu 24.04 на виртуалке с 4 ядрами, Python 3.12.3, Node 22, ffmpeg 6.1.1, earshot на коммите 15da817. requirements.txt жёстко фиксирует только faster-whisper==1.1.0, остальное выбирает pip. 11 октября 2026 года он выбрал huggingface_hub 2.2.0 и onnxruntime 1.31.0.
Чтобы следить за сетью, я запускал всё дерево процессов, Node, ffmpeg и сайдкар на Python, под strace -f -e trace=connect. Чтобы выключить сеть, машину я не трогал. strace умеет заставить каждый connect() трассируемых процессов падать с ENETUNREACH. Такую ошибку система отдаёт, когда до сети нет маршрута. Звук я взял из Open Speech Repository. Это 10 записей гарвардских фраз, всего 342 секунды, я прогнал их через earshot --source file.
Без сети он работает
Все 10 записей расшифровались с отрезанной сетью. Текст совпал с прогоном при включённой сети, 10 из 10. Доля ошибочных слов относительно опубликованного текста фраз вышла 13,1%, в основном потому, что записи телефонного качества, 8 кГц. “The birch canoe slid” распозналось как “The birch can use lid”. Запуск занимал 1,6 секунды без сети и 1,9 с сетью, без единого предупреждения в логе.
Цифра 145 МБ верна только для одного файла. model.bin весит 145 216 508 байт, папка модели 148 МБ. Виртуальное окружение Python, в котором она работает, весит 472 МБ. 132 МБ из них это PyAV со своими библиотеками ffmpeg. Для декодирования earshot их не использует, он запускает системный ffmpeg и отдаёт сайдкару сырые сэмплы. Вместе с npm install на диске выходит 658 МБ.
С сетью он спрашивает Hugging Face при каждом запуске
Каждый запуск сайдкара открывал соединение TLS с huggingface.co. С включённым логом запросов виден ровно 1 запрос:
GET https://huggingface.co/api/models/Systran/faster-whisper-base.en/revision/main
faster-whisper спрашивает Hub, нет ли у модели новой ревизии, прежде чем открыть файлы из кеша. О моём звуке туда ничего не уходит, но Hugging Face видит мой IP и название модели при каждом запуске earshot. Переменная HF_HUB_OFFLINE=1 это отключает. С ней сайдкар сделал 0 соединений с Hugging Face.
При плохой сети ещё хуже. Я направил Hub на адрес, который отбрасывает пакеты. Сайдкар был готов только через 135 секунд вместо 1,6. Столько Linux ждёт, прежде чем бросить попытку соединения по TCP. Потом я поставил прокси, который принимает соединение и ничего не отвечает, как мёртвая страница входа в гостиничном вайфае. Через 900 секунд сайдкар всё ещё грузился и я его убил. Клиент HTTP в huggingface_hub 2.2.0 создаётся с timeout=None.
Второй сервер это Microsoft
Дерево процессов соединялось ещё и с mobile.events.data.microsoft.com. Соединение шло из потока сайдкара. Адрес вшит в скомпилированное расширение onnxruntime для Python.
faster-whisper использует onnxruntime для одного: фильтра голосовой активности Silero. Сайдкар earshot включает этот фильтр по умолчанию, хотя в самом сайдкаре уже есть флаг --no-vad с подсказкой “earshot already segments”. Node режет звук на высказывания до того, как его увидит Python, так что фильтр повторяет нарезку, которую Node уже сделал.
onnxruntime шлёт телеметрию на Linux начиная с версии 1.29.0. Я просмотрел пакеты wheel: в 1.28.0 и более ранних адреса коллектора нет, в 1.29.0, 1.30.0 и 1.31.0 он есть. Установка earshot с 1.28.0 сделала 0 соединений с Microsoft. Когда я писал README в середине августа, pip ставил 1.28.0, так что значок был правдой. Репозиторий я открыл 17 августа. 1.29.0 появился на PyPI поздним вечером того же дня по UTC. В моём коде и в requirements.txt не изменилось ничего. Со следующей установки значок стал неправдой.
Справедливости ради, в заметках к выпуску 1.29.0 об этом есть одна строка, там же назван выключатель, ORT_DISABLE_TELEMETRY=1. Просто я не читал заметки к выпуску зависимости моей зависимости.
Сессия без сети на 1.31.0 оставила в локальной очереди 1 событие под названием ProcessInfo. Я прочитал его прямо в очереди. В нём хеш идентификатора устройства, ОС и её версия, модель процессора, объём памяти, тип виртуалки, тип сети (“Wired”) и полный путь к исполняемому файлу Python, в котором есть ваше имя пользователя, если клон лежит в домашней папке. Исходный идентификатор устройства, без хеша, это UUID, который onnxruntime пишет в ~/.cache/Microsoft/DeveloperTools/.onnxruntime/deviceid.
Выдернутый кабель только откладывает отправку
Когда сеть отрезана, событие не выбрасывается. onnxruntime хранит его в файле SQLite рядом с файлом идентификатора, onnxruntime.db. В чистой папке кеша 2 сессии без сети оставили в очереди 2 события. Потом я запустил 1 сессию с сетью. За эту сессию сайдкар соединился с Microsoft. После неё 2 события, записанные без сети, из очереди исчезли.
Расшифровка остаётся на машине, но запись о машине и сессии ждёт в очереди и, скорее всего, уйдёт при первом же выходе в сеть.
С обеими переменными всё дерево сделало 0 соединений и не записало файл идентификатора. Расшифровка не изменилась.
Что исправить
Сайдкар запускается из whisperRecognizer.ts с окружением, где задан HF_HOME. В комментарии там написано: “Nothing here needs to phone home once the model is on disk.” Обеим переменным место именно там. Пока их нет в репозитории, задавайте их сами перед запуском earshot. Закрепить версию onnxruntime ниже 1.29 тоже помогло бы, но следующая транзитивная зависимость может сделать то же самое и я опять узнаю об этом случайно.
Значок должен стать тестом: прогон под strace с отрезанной сетью, который валится на любом connect() к IP-адресу. Такого теста у меня не было, поэтому изменение в onnxruntime прошло незаметно.
Чего я не проверял
Проверял только Linux. В заметках к 1.29.0 сказано, что выключатель действует на телеметрию вне Windows. Трафик TLS я не расшифровывал, так что о том, что дошло до Microsoft, я сужу по локальной очереди. Я не доказал, что события, записанные без сети, исчезли из очереди именно потому, что отправка удалась. В повторном прогоне они тоже исчезли, но 2 новых события из сессии с сетью остались ждать. Проверки с прокси и с потерей пакетов прогнаны по 1 разу. Время запуска мерил по 3 прогона на каждый вариант, долю ошибочных слов по 1 прогону на запись.