Сегодня мы на Product Hunt! Оставить комментарий
Вышла версия Authenticator 1.16.0. Что нового
Технический обзор для тех, кто проверяет безопасность. Всё изложенное сверяется с исходным кодом — включая то, что нас не красит: мы это называем, а не опускаем.
Это расширение Chrome на Manifest V3, которое генерирует коды TOTP локально с помощью OTPAuth. Генерация кодов, хранение аккаунтов и их шифрование обходятся без бэкенда, и учётных записей пользователей нет. У нас есть один сервер, api.authenticator.sh, — он нужен для бесплатного тарифа и Pro и описан ниже; он никогда не получает ни секретов, ни названий аккаунтов, ни названий сервисов, ни кодов.
Этот сервер не хранит ключевого материала ни для чьих аккаунтов: нет депонирования ключей и нет пути восстановления, который контролировали бы мы. Если пользователь забыл пароль и потерял код восстановления, мы не сможем помочь — так задумано.
Защита паролем необязательна и по умолчанию выключена. Когда она включена:
Данные никогда не шифруются ключом, выведенным из пароля, напрямую. Записи шифрует случайный мастер-ключ; вывод PBKDF2 только оборачивает этот мастер-ключ. Отсюда два следствия, и оба важны:
При включённой защите паролем шифруется вся запись аккаунта, кроме идентификатора и отпечатка — в том числе название сервиса, так что по украденному профилю не понять, аккаунтами каких сервисов владеет пользователь. При включении расширение шифрует записи, проверяет их расшифровкой и сравнением и только затем удаляет все копии в открытом виде: из локального хранилища браузера, из синхронизации и из всех семи ротируемых резервных снимков.
Говорим прямо, потому что для вашей оценки это существенно:
Коротко: это защищает данные в состоянии покоя. Устройство, которое уже находится под контролем атакующего во время работы, так защитить нельзя — и мы этого не обещаем.
Манифест объявляет пять разрешений и ни одного host-разрешения:
В расширении нет content-скриптов, нет разрешений tabs, cookies и webRequest и нет web-accessible-ресурсов. Ничего нашего на странице не существует, пока вы не попросите там код; то, что выполняется потом, внедряется для одной этой вкладки и одного этого вызова, по разрешению, которое даёт ваш собственный клик, и не оставляет после себя ничего.
Сканирование QR-кода камерой работает на странице расширения, открытой во вкладке, а не во всплывающем окне: popup уничтожается при потере фокуса, а запрос разрешения его как раз отбирает. Для этого не нужно разрешение в манифесте: используется штатный запрос камеры браузера, который выдаётся на origin расширения и отзывается в настройках сайта, и он никогда не появляется, пока пользователь не откроет сканер. Видео декодируется локально, никуда не передаётся и не сохраняется.
Начиная с 1.14.0 расширение раз в сутки связывается с api.authenticator.sh — при запуске браузера или уже после того, как окно расширения отрисовано, так что показ кода этот запрос никогда не задерживает. Запрос — закрытый список полей: его собирает одна функция в src/billing/sync.ts, а тест следит, чтобы в нём не появилось ничего сверх этого списка:
Никогда не передаются: секреты, названия аккаунтов, названия сервисов (issuer), коды и сайты, на которых они используются. Страну сервер определяет по запросу на своём edge-узле и IP-адрес не сохраняет. Страница удаления получает тот же идентификатор установки в параметре ?i=: она сообщает серверу, что установка удалена, и связывает с ней необязательный ответ на опрос.
Для этого в chrome.storage.local хранятся идентификатор установки, последние подписанные политика и лицензия, лицензионный ключ этой установки (введённый вручную или полученный после покупки в ней), счётчики открытий по дням, пока их не получил сервер, и события в очереди — никаких данных аккаунтов. Пока включена синхронизация, признак того, что установка сделана до введения биллинга, и этот лицензионный ключ попадают ещё и в Chrome Sync — чтобы другие профили Chrome этого пользователя их подхватили и сами включили Pro; если синхронизацию выключить, оба удаляются из Chrome Sync, а при повторном включении возвращаются.
Что приходит в ответ и как расширение с этим обращается:
Лимит — не DRM: код открыт, и в форке его можно убрать. Невозможным должно быть другое: чтобы один человек получил или использовал чужую лицензию или сделал ограниченной чужую установку. Это уязвимость, и на неё распространяется наша политика безопасности.
Самостоятельно расширение делает запросы двух видов: проверку часов и ежедневный биллинговый запрос. Всё остальное ниже происходит потому, что пользователь на это нажал. Manifest V3 запрещает удалённый код, и ничто из перечисленного не передаёт ни аккаунты, ни секретные ключи, ни сгенерированные коды. Полный список:
| Хост | Когда | Передаёт данные пользователя? |
|---|---|---|
| time.akamai.com timeapi.io cloudflare.com | Проверка расхождения часов, кэшируется; если ни один источник недоступен, об этом сказано в настройках. При сбитых часах TOTP ломается. | Нет |
| authenticator.sh | Страница приветствия при установке; страница обратной связи при удалении — с идентификатором установки в адресе; страницы помощи и поддержки, когда их открывают из окна расширения. | Идентификатор установки — только на странице удаления; в остальном нет (обычные логи веб-сервера) |
| api.authenticator.sh | Биллинговый запрос раз в сутки; запросы оплаты, лицензии и портала, когда пользователь покупает Pro, вводит ключ, восстанавливает покупку или управляет подпиской. | Поля, перечисленные выше, — но никогда данные аккаунтов. Лицензионный ключ или адрес электронной почты — только если пользователь сам их вводит |
| checkout.stripe.com billing.stripe.com | Stripe Checkout и клиентский портал, открываются во вкладке, когда пользователь покупает Pro или управляет подпиской. | То, что покупатель там вводит. Действуют собственные условия Stripe и Link |
| chromewebstore.google.com | Страница в магазине, открывается во вкладке только по клику: форма отзыва — из предложения оценить. | Нет (для Google это обычный заход на страницу магазина) |
Ни шрифты, ни скрипты, ни стили, ни изображения не загружаются со сторонних хостов. Всё, что нужно для отрисовки интерфейса, лежит внутри пакета.
Ничего из перечисленного не нужно принимать на веру. Расширение с открытым исходным кодом, и каждый релиз воспроизводим:
.crx из Chrome Web Store и распакуйте егоnpm ci && npm run build на Node 20 LTSdist/ с распакованным пакетомSHA-256 для каждого файла в собранном dist/ публикуется с каждым релизом на GitHub в виде SHA256SUMS-v<version>.txt, в формате sha256sum — так что шаг 4 сводится к одному запуску sha256sum -c. Расхождения должны ограничиваться порядком файлов внутри архивов и пробелами в минифицированном выводе на разных патч-версиях Node.
Зависимостей в рантайме намеренно мало — OTPAuth, React, jsQR, Zustand, Framer Motion и Lucide — и все они зафиксированы закоммиченным lock-файлом. SBOM в формате CycloneDX предоставляем по запросу.
Смотреть исходный кодМы небольшой независимый разработчик. Если что-то из перечисленного для вашего процесса обязательно, лучше узнать об этом сейчас, а не на третьей неделе оценки:
Взамен мы предлагаем полную прозрачность исходного кода, проверяемую сборку, минимальный набор разрешений и сервер, которому ваши секреты, названия аккаунтов и коды просто никогда не отправляются.
Сообщения об уязвимостях и вопросы по безопасности: security@authenticator.sh. Политика раскрытия, границы применимости, сроки ответа и условия safe harbour — на странице политики безопасности; что хранится и что покидает устройство — в политике конфиденциальности.
Мы готовы заполнить письменную анкету оценки поставщика или разобрать код вместе с вашей командой безопасности.
По всему, что не касается безопасности — установка, восстановление доступа, вопросы по оплате при развёртывании — см. поддержку. Пожалуйста, не отправляйте туда сообщения об уязвимостях: этот ящик не считается конфиденциальным.