Skip to content

Безопасность

Обновлено: 23 октября 2026 г.

Что BrowserHat подписывает и каким ключом, что пока не подписано, что десктопное приложение отправляет с вашего компьютера и как хранится хранилище паролей. Где что-то не сделано или не измерено, эта страница так и говорит. Обе подписи можно проверить самостоятельно командами ниже.

Обновления приложения: подпись Ed25519

У каждого бандла обновления есть манифест: список файлов с хэшем SHA-256 и размером каждого. Манифест подписан Ed25519. Перед установкой обновления приложение проверяет подпись открытым ключом, встроенным в установленное приложение (он меняется только полной переустановкой), проверяет, что манифест выпущен для предложенной версии и канала и новее работающей версии (отката нет), и сверяет каждый файл с его хэшем.

Маленький файл-указатель с номером текущей версии (latest.json) не подписан. Он может только предложить версию; что установится, решает подписанный манифест.

Браузерное ядро BrowserHat скачивается архивом, чей SHA-256 записан внутри подписанного бандла обновления.

Открытый ключ обновлений, SHA-256 ключа в DER: 5cbf329c5b52777118f96ff71314ed6457438971aa722b5c1eae16ab97cc5cab. Проверить текущий стабильный манифест самостоятельно (оболочка Linux, OpenSSL 3.0 или новее):

# Открытый ключ обновлений (Ed25519)
printf -- '-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAlE8Fc8HY6Kut8uC1CzRuoNmOjI2NJDVhgtYkVFSEug4=\n-----END PUBLIC KEY-----\n' > ota.pem
openssl pkey -pubin -in ota.pem -outform DER | sha256sum
# ожидается: 5cbf329c5b52777118f96ff71314ed6457438971aa722b5c1eae16ab97cc5cab

# Текущий стабильный манифест и его подпись
V=$(curl -s https://dl.browserhat.com/bundles/stable/latest.json | sed 's/.*"bundleVersion":"\([^"]*\)".*/\1/')
curl -sO https://dl.browserhat.com/bundles/stable/$V/manifest.json
curl -sO https://dl.browserhat.com/bundles/stable/$V/manifest.sig
base64 -d manifest.sig > manifest.sig.bin
openssl pkeyutl -verify -pubin -inkey ota.pem -rawin -in manifest.json -sigfile manifest.sig.bin
# ожидается: Signature Verified Successfully

# Или через Node.js вместо двух последних команд:
node -e "const c=require('crypto'),f=require('fs');console.log(c.verify(null,f.readFileSync('manifest.json'),c.createPublicKey(f.readFileSync('ota.pem')),Buffer.from(f.readFileSync('manifest.sig','utf8').trim(),'base64')))"
# ожидается: true

Пакеты маркетплейса: подпись ключом mk-2026-1

Каждый пакет маркетплейса подписывается отдельным ключом Ed25519 - mk-2026-1 - в момент, когда его публикуют наши сотрудники. Подпись покрывает короткое утверждение: имя пакета, версию, движок браузера, SHA-256 и размер. Десктопное приложение каждый раз собирает это утверждение заново из байтов, которые оно действительно скачало или держит в кэше, и отказывается ставить пакет, если подписи нет, она сделана неизвестным ключом или не сходится.

От чего это защищает: от подмены пакета после публикации - в нашем файловом хранилище, в сервисе доставки, на CDN или в кэше приложения. От чего не защищает: от захвата нашего сервера или учётной записи сотрудника. Тот, кто может там опубликовать пакет, может получить для него и подпись. Старые подписанные версии тоже остаются годными: захваченный сервис доставки всё ещё может отдать более старую, честно подписанную версию того же расширения.

Сейчас в маркетплейсе нет сторонних пакетов. Smart Paste входит в само приложение и покрыт подписью обновлений.

Открытый ключ маркетплейса mk-2026-1, SHA-256 ключа в DER: 453da342a0c039051188ec4e75a5f8c40cad7ce9503e01797d2d647dff79c05a. Чтобы проверить пакет, возьмите extension_id, version, engine и signature из ответа доставки маркетплейса и убедитесь, что его signature_key_id - mk-2026-1, сохраните скачанный пакет как package.zip и выполните:

# Открытый ключ маркетплейса mk-2026-1 (Ed25519)
printf -- '-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEASg//5q+eoEmkz9gCUCg6IbGnf7N9zgVDaQzcjuIodAw=\n-----END PUBLIC KEY-----\n' > marketplace.pem
openssl pkey -pubin -in marketplace.pem -outform DER | sha256sum
# ожидается: 453da342a0c039051188ec4e75a5f8c40cad7ce9503e01797d2d647dff79c05a

# Из ответа доставки (его signature_key_id должен быть mk-2026-1):
# SLUG=extension_id VERSION=version ENGINE=engine SIG=signature
SHA=$(sha256sum package.zip | cut -d' ' -f1)
SIZE=$(wc -c < package.zip | tr -d ' ')
printf '{"engine":"%s","sha256":"%s","size":%s,"slug":"%s","v":1,"version":"%s"}' "$ENGINE" "$SHA" "$SIZE" "$SLUG" "$VERSION" > statement.json
printf '%s' "$SIG" | base64 -d > statement.sig
openssl pkeyutl -verify -pubin -inkey marketplace.pem -rawin -in statement.json -sigfile statement.sig
# ожидается: Signature Verified Successfully

Где лежат ключи подписи

Закрытый ключ обновлений и закрытый ключ маркетплейса не хранятся в исходном коде. Оба лежат на нашем боевом сервере, который также собирает выпуски и раздаёт загрузки; кроме того, у владельца есть копии ключа обновлений вне этого сервера. Годная подпись доказывает, что файл вышел с этого сервера неизменным. Если захвачен сам сервер, подпись вас не защитит.

Что пока не подписано

У установщика для Windows нет сертификата подписи кода Authenticode, поэтому Windows SmartScreen и антивирусы могут о нём предупреждать. Устанавливая полное обновление, приложение сверяет установщик с хэшем SHA-512 из нашей службы обновлений.

Пакет SDK на dl.browserhat.com сопровождается файлом SHA-256 с того же сервера, подписи нет.

Расширения из Chrome Web Store сверяются с SHA-256, который сервер обновлений Google отдаёт для файла .crx. Саму подпись CRX BrowserHat не проверяет.

Файл расширения, который вы загружаете сами через SDK, - ваш собственный файл; BrowserHat его не проверяет.

Что отправляет десктопное приложение

Пока вы вошли в аккаунт - сигнал присутствия на наш сервер каждые 15 секунд. Он держит сессию и позволяет заблокировать аккаунт, используемый для злоупотреблений. Сервер записывает ваш IP-адрес вместе с событиями входа (запрос кода, неудачный и успешный вход, выход) и с запуском и закрытием приложения.

Инвентарь профилей - при изменении и не реже раза в 30 минут: для каждого профиля его идентификатор, операционная система, движок браузера, страна прокси, время создания и последнего запуска. Без имён профилей. Это служебная телеметрия; переключатель аналитики её не выключает.

Проверка обновлений при запуске и каждые 2 минуты: платформа, архитектура, канал обновлений, версия приложения и версия Electron.

Резервная копия ключа, если она включена (см. «Хранилище паролей»): зашифрованная копия ключа данных - загружается, когда нужно (новый ключ, другой аккаунт или пропавшая копия), и сверяется с нашим сервером не чаще раза в 6 часов.

Политика команды: пока вы вошли в аккаунт, приложение читает с нашего сервера список ваших команд раз в 6 часов и при переходе в окно приложения (не чаще раза в 10 минут), чтобы узнать, требует ли команда резервную копию ключа. Это происходит и при включённой, и при выключенной копии.

Логотипы сайтов в хранилище паролей: локальная служба спрашивает у icons.browserhat.com домен записи (только домен), повторно через 30 дней, а если у домена логотипа не нашлось, через 24 часа. Выключается переключателем «Логотипы сайтов» в хранилище.

Продуктовая аналитика через PostHog (США): запуск и закрытие приложения, вход и выход, создание, запуск, остановка, удаление, восстановление и окончательное удаление профиля, события обновлений, просмотренные экраны, установки и удаления из маркетплейса. События привязаны к идентификатору и почте вашего аккаунта и уходят пачками примерно раз в 10 секунд. Включена по умолчанию; выключается в «Настройках» («Аналитика использования») или переменной BROWSERHAT_TELEMETRY=off.

Маркетплейс: при открытии загружается каталог с market.browserhat.com. Запросы каталога и установки идут с данными входа в ваш аккаунт; установка пакета также отправляет движок браузера, версию ядра и идентификатор профиля, для которого он ставится.

Проверки соединения. Когда вы проверяете или сохраняете прокси, локальная служба обращается через этот прокси к ip-api.com и ipwho.is, чтобы узнать адрес выхода и его страну; если ipwho.is не отвечает, она спрашивает вместо него ipinfo.io, через тот же прокси. Та же проверка обращается через прокси ещё к os.browserhat.com, нашему серверу, который по TCP-соединению самого выхода узнаёт, на какой операционной системе он работает, и к www.google.com, чтобы проверить, что через прокси работает HTTPS. Когда вы проверяете сервер VLESS, а также при запуске привязанного к нему профиля, пока его часовой пояс ещё неизвестен, те же запросы идут через туннель VLESS, и эти сервисы видят адрес выхода туннеля. Пока запущен профиль с прокси на липкой сессии, служба повторяет эту проверку примерно через 20 и 60 секунд после запуска, а затем каждые 5 минут, чтобы заметить смену адреса выхода. Перед запуском профиля с липкой сессией ProxyHat, у которой истекло время сессии, она проверяет прокси заново; если включено «Подбирать выход под ОС персоны» и ОС выхода не совпадает, она берёт новую сессию и проверяет снова, всего до 5 проверок. Пока открыт экран сессий, она также напрямую спрашивает у ipwho.is ваш собственный адрес в интернете (и ещё через системный прокси, если он задан; ответ используется повторно 90 секунд); если ipwho.is не отвечает, она спрашивает вместо него ipinfo.io, тоже напрямую с вашего настоящего адреса. Если на компьютере найден VPN, она спрашивает ещё api.ipify.org, checkip.amazonaws.com, icanhazip.com и ifconfig.me, а если они называют другие адреса, узнаёт страну и сеть каждого напрямую у ipwho.is, а если он не отвечает, тоже напрямую у ipinfo.io. Та же прямая проверка выполняется и при запуске профиля без прокси, когда включено «Гео прямых сессий по выходу VPN», если свежего ответа нет в кэше.

Проверка UDP перед запуском. Перед запуском профиля, чей прокси - SOCKS5 или туннель VLESS, если WebRTC у него не выключен, локальная служба проверяет, пропускает ли прокси UDP. Через UDP-ассоциацию прокси (SOCKS5 UDP ASSOCIATE) она отправляет запрос STUN на stun.l.google.com:19302, а если Google не отвечает, на stun.cloudflare.com:3478, и через вторую ассоциацию - DNS-запрос имени этого сервера на 1.1.1.1:53. Эти серверы видят адрес выхода прокси, а не ваш. Удачный результат используется повторно 30 минут; прокси, который не пропускает UDP, повторно не проверяется 24 часа. Замер задержки сервера VLESS (на экране VLESS автоматически для серверов списка или когда вы проверяете подключение VLESS у профиля) отправляет HTTP-запрос на cp.cloudflare.com/generate_204 через туннель этого сервера.

Только по вашему действию: отчёты об ошибках (снимок окна приложения, копия окна приложения с очищенными полями паролей, последние ошибки и адреса запросов приложения), сообщения ИИ-ассистенту и облачная синхронизация профиля, для которого вы её включили.

Ничто из перечисленного не содержит содержимого страниц, которые вы открываете в профилях, посещённых адресов, ваших cookie и паролей, с одним исключением: облачная синхронизация загружает профиль целиком, вместе с cookie. Синхронизированные профили зашифрованы ключами, которые хранит наш сервер, поэтому это не сквозное шифрование: тот, кто управляет нашим сервером, может их расшифровать.

Куда обращается сам браузер

Браузерное ядро BrowserHat собрано из Chromium. Мы измерили его 20 октября 2026 года: сборка ядра dev.55, запущенная напрямую, а не через приложение (то есть без настроек, которые приложение ему передаёт), новый профиль, без прокси, пустая вкладка, 10 минут простоя. За это время оно обращалось только к службам Google: обновлятель компонентов (update.googleapis.com, edgedl.me.gvt1.com), обновление списков Safe Browsing (safebrowsing.googleapis.com), проверка времени (clients2.google.com), accounts.google.com и www.google.com. К серверам BrowserHat оно не обращалось.

В тот же день мы повторили замер с прокси, переданным ядру так же, как приложение передаёт прокси профиля (--proxy-server), через локальный прокси, записывающий каждый хост: все эти запросы прошли через прокси, ни одно соединение его не обошло. Единственный прямой сокет - стандартная проверка доступности IPv6 в Chromium: она направляет сокет UDP на адрес DNS Google и не отправляет данных. Эти фоновые службы мы оставляем включёнными, как в обычном Chrome, поэтому у профиля с прокси они ходят в Google с адреса прокси (около 140 КБ за 10 минут в нашем замере).

WebRTC и UDP. У профиля, чей прокси (SOCKS5 или туннель VLESS) пропускает UDP, WebRTC идёт по UDP через прокси: ядро отдаёт свой UDP посреднику (релею), который локальная служба запускает на 127.0.0.1, а посредник пересылает его через UDP-ассоциацию прокси (SOCKS5 UDP ASSOCIATE). Имена серверов STUN и TURN ядро тогда тоже узнаёт через этого посредника: DNS-запрос имени уходит на 1.1.1.1:53 через прокси. Во всех остальных случаях с прокси (прокси HTTP или HTTPS, Tor, прокси без UDP или адрес выхода UDP, отличный от адреса выхода TCP) WebRTC идёт только по TCP через прокси и не отправляет UDP.

Свой DNS. Если для профиля без прокси выбран свой DNS-сервер (DoH), ядро отправляет имена сайтов, которые открывает этот профиль, выбранному серверу по HTTPS, а не системному резолверу (провайдеру или VPN). Имя самого DoH-сервера оно один раз узнаёт через системный резолвер, если сервер не указан IP-адресом. Если этот сервер не отвечает, страницы не открываются: их имена не уходят вместо него системному резолверу (Chromium может спросить у него connectivitycheck.gstatic.com, чтобы проверить связь). С прокси, VLESS или Tor настройка ничего не меняет, и имена разрешаются как у любого профиля с таким подключением: на выходе, кроме прокси SOCKS4, который имён не передаёт, поэтому отвечает системный резолвер. Профили Firefox настройку не учитывают. На компьютере с Windows в домене или с родительским контролем Chromium тоже может её не учесть и спрашивать системный резолвер.

Хранилище паролей и данные на компьютере

Хранилище паролей, импортированные cookie, настройки прокси и VPN-подписки хранятся на вашем компьютере зашифрованными AES-256-GCM, под 32-байтовым ключом данных, который создаётся один раз на установку.

Без мастер-пароля ключ данных защищает операционная система (DPAPI в Windows) плюс наш дополнительный слой. Это останавливает вредоносную программу, которая только копирует файлы. Это не останавливает вредоносную программу, работающую под вашей учётной записью: она может снять защиту ключа через операционную систему в любой момент.

С мастер-паролем клиента ключ данных защищён ключом, выведенным из пароля через scrypt (N=2^16, r=8, p=1), и вы получаете ключ восстановления. Копия ключа под защитой операционной системы удаляется. Шифрование и в этом случае не защищает от вредоносной программы, работающей на компьютере, пока приложение разблокировано.

Резервная копия ключа у BrowserHat включена по умолчанию. Копия ключа данных зашифрована на ключ восстановления BrowserHat, закрытая половина которого хранится офлайн, и лежит на нашем сервере; поддержка может вернуть вам доступ после проверки личности. Поэтому хранилище не является строго zero-knowledge. Копию можно выключить в «Настройки» > «Безопасность» > «Резервная копия ключа у BrowserHat»; тогда она удаляется из нашей базы данных, а ежедневные резервные копии базы, где она ещё есть, удаляются примерно через две недели. Пока по этому устройству открыта заявка на восстановление, копия остаётся до закрытия заявки. Владелец команды может сделать копию обязательной для участников.

Хранилище паролей не синхронизируется с облаком.

Профили и вход

Каждый профиль работает со своей папкой данных браузера (cookie, хранилище) и своей настройкой прокси.

Вход беспарольный: одноразовый код на вашу почту.

Пароль профиля. Пароль на профиле - это ворота доступа в приложении и локальном демоне, а не шифрование: файлы профиля на диске им не шифруются, поэтому без мастер-пароля клиента они защищены ровно так же, как без пароля профиля. Без пароля или разблокировки раньше в этом сеансе профиль нельзя запустить (и из SDK и MCP тоже), выгрузить, скопировать, переименовать или отправить в корзину, а его cookie и сохранённые пароли нельзя ни прочитать, ни заменить. Одно исключение, только пока у профиля включена облачная синхронизация: приложение может снять его зашифрованный снимок синхронизации без пароля в течение 10 минут после конца сессии или смены пароля, а после автоматической блокировки - и до конца уже идущей сессии. В это окно снимок может снять и любая программа на этом компьютере, у которой есть ключ локального демона. Значит, запись cookie и паролей из SDK и MCP в такой профиль сначала требует разблокировки. Выход из аккаунта или его удаление сначала останавливают каждый запущенный профиль с паролем и, если у него включена облачная синхронизация, публикуют его сессию, а профиль, который в этот момент запускался, закрывается сразу после запуска, так что его браузер и локальный порт отладки открытыми не остаются. Автоматическая блокировка (простой, блокировка экрана, сон или блокировка вручную) только закрывает доступ: запущенные профили с паролем работают, пока вы их не закроете, если в настройках не включено «Останавливать сессии при блокировке» или «Закрывать профили с паролем при блокировке». Со второй настройкой автоматическая блокировка закрывает каждый запущенный профиль с паролем и, если у него включена облачная синхронизация, публикует его сессию; остальные профили продолжают работать. Разблокировка действует, пока приложение не заблокировано, вы не вышли из аккаунта и не закрыли приложение. Без пароля приложение и локальный демон показывают имя профиля, систему, браузер, метки и статус, но отпечаток, прокси и подключение скрыты, а настройки сессии открываются только после пароля. Восстановления через наш сервер нет: с мастер-паролем клиента забытый пароль профиля можно снять (доказательство снятия годится только для текущего пароля), без него остаётся только удалить профиль, а удаление пароля не требует. Для того, у кого ключ облачных версий профиля, пароль не преграда: наш сервер (через депозит ключа) и участники вашей команды могут открыть синхронизированный снимок целиком, вместе с cookie, и перебирать пароль офлайн, поэтому защита не крепче самого пароля. Участник команды с этим ключом может и опубликовать версию, которая снимает пароль; если её отметка времени опережает часы не больше чем на сутки, она побеждает на всех устройствах. Устройства сводят смену пароля по своим часам. Если часы устройства спешат больше чем на сутки, снятие пароля, пока это опережение длится, не доходит до других устройств: там профиль остаётся закрытым прежним паролем. Выход - снять пароль ещё раз на каждом устройстве, где он остался, или снять ещё раз после того, как часы сойдутся; такое снятие подействует не позже чем через 30 дней. Спешащие часы могут и дать раннему снятию победить пароль, заданный позже на другом устройстве до его синхронизации, а устройство, чьи часы спешат больше чем на 30 дней, пароли, заданные позже, игнорирует. Пароль своего профиля задаёте, меняете и снимаете вы; у профиля, полученного от команды, это может только владелец команды, роль подтверждает наш сервер, а участники команды не могут. Владелец команды, не знающий пароль, заданный участником, снимет его только с мастер-паролем своего клиента (снятием выше); без мастер-пароля - не снимет.

Сообщение об уязвимости

Если вы обнаружите проблему безопасности, пожалуйста, свяжитесь с нами по адресу [email protected], чтобы мы могли разобраться. Формальная политика раскрытия будет опубликована до общей доступности.