К содержанию
ActionPulse

Браузерный токен и серверный секрет

Модель угроз для ключей приёма ActionPulse — что сможет посторонний с браузерным токеном, что произойдёт при утечке серверного ключа, как хранить секрет и что делать при подозрении на утечку.

Кому
Разработчикам и безопасникам
Проверено
На этой странице

У приёма событий два разных ключа с разными границами доверия, и путаница между ними — самая дорогая ошибка интеграции. Браузерный токен pp_bt_… публичен по устройству: его видит каждый посетитель сайта, и это нормально. Серверный ключ pp_sk_… — секрет: его утечка означает, что кто-то может писать в ваш проект «достоверные» бизнес-факты.

Здесь — модель угроз, а не справочник по областям действия. Что каждому ключу разрешено по эндпоинтам, какие бывают коды отказа и как выпустить ключ — в типах ключей приёма и в получении ключей.

Модель угроз простыми словами

Начнём с того, чего не может ни один ключ приёма: читать данные. У ключей приёма нет ни одной области действия на чтение аналитики. С ключом нельзя открыть отчёт, выгрузить события, посмотреть список людей, увидеть запись сессии, зайти в кабинет или дотянуться до биллинга. Кабинет и его API работают на другом механизме — сессии пользователя.

Отсюда следует главное: утечка ключа приёма — это угроза целостности данных и расхода квоты, а не утечка ваших аналитических данных.

Что сможет посторонний С браузерным токеном С серверным ключом
Отправлять встроенные события pp.* да нет
Отправлять свои события только имена из списка токена любые, кроме pp.*
Отправлять подтверждённые бизнес-факты (оплаты, заказы, возвраты) нет да
Записывать свойства человека через identify да да
Связывать идентификаторы через alias нет да
Присылать поддельные записи сессий да нет
Читать публичную конфигурацию сбора да нет
Читать любые ваши данные нет нет
Расходовать месячную квоту события да да

Что это значит на практике.

Браузерный токен в чужих руках — это мусор в отчётах: накрутка кликов и просмотров, поддельные записи сессий, произвольные свойства у существующих user_id, расход месячной квоты вплоть до отказов quota_exceeded. Неприятно и требует чистки, но ни один финансовый показатель так не подделать: имена registered, subscription_started, payment_succeeded, order_paid, refund и остальные подтверждённые факты браузеру закрыты на уровне политики событий, а не настройкой.

Утечка серверного ключа — это уже подделка бизнес-фактов. Ключ принимается для тех самых имён, на которых строятся выручка и конверсии. Плюс к этому он несёт alias: чужой запрос может склеить два разных человека в одного, и граф идентичности проекта будет искажён. Именно поэтому серверный ключ никогда не должен покидать бэкенд.

Почему браузерный токен публичен и это нормально

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

<script
  src="https://YOUR_ACTIONPULSE_HOST/v1/t.js"
  data-token="YOUR_BROWSER_TOKEN"
  data-consent="required"
  defer
></script>

Значение в разметке — ожидаемое состояние. В браузерное хранилище трекер токен не складывает: там лежат только идентификаторы посетителя, метка сессии и очередь доставки событий. Значение подставляет ваш шаблон или сборка, а трекер читает его из атрибута тега или из параметра инициализации.

Считать браузерные события недоверенным вводом — заявленное свойство продукта, а не оговорка. Всё, что приходит публичным токеном, обрабатывается как ввод неизвестного происхождения: с ограниченными правами, поэлементной проверкой и жёсткими лимитами.

Что удерживает браузерный токен

Публичность компенсируется четырьмя ограничениями, и все четыре задаются в момент выпуска.

Область действия. У токена ровно пять прав: чтение публичной конфигурации сбора, запись событий, identify, запись сессий, захват элемента выбором на странице. Права identity:alias у него нет вообще — не «выключено по умолчанию», а отсутствует как возможность. Проверяются одновременно тип ключа и явная область действия: область сама по себе никогда не расширяет тип.

Список разрешённых origin. От 1 до 32 точных адресов. Проверка работает так: приём берёт заголовок Origin запроса, приводит его к каноническому виду (регистр схемы и хоста, завершающий слеш, точка на конце имени хоста, порт по умолчанию) и сравнивает на точное совпадение со списком токена. Маски запрещены и при выпуске, и при сравнении. Пустой или некорректный список означает отказ всегда — токен без origin нерабочий. Несовпадение даёт 403 с кодом credential_origin_forbidden, и присланный origin в ответе не отражается. Подробности нормализации — в CORS и origin.

Срок действия. Браузерный токен обязан иметь срок; если он не задан явно, применяется 90 дней с момента выпуска. Истёкший токен отвечает 401 так же, как отозванный. Срок виден в списке ключей проекта в колонке «Истекает»; отдельного напоминания об истечении продукт не присылает — поставьте себе календарное.

Список своих имён событий. От 1 до 128 точных имён. Если при выпуске список не задан, применяется узкий стартовый список из одного имени — signup_completed. Любое другое своё имя отклоняется на уровне отдельного элемента батча с кодом credential_event_forbidden. Встроенные события pp.* списком не ограничены и работают независимо от него. Если событие объявлено в реестре проекта как серверное, браузерный токен его не отправит даже при наличии имени в списке — реестр авторитетнее.

Сверх этого действуют лимиты частоты на префикс ключа и месячная квота событий тарифа, поэтому «бесконечной» накрутки с одного токена не получится.

Как хранить серверный ключ

Единственная защита серверного ключа — секретность. Правила простые.

Можно:

  • переменная окружения процесса, значение подставляется из менеджера секретов (Vault, AWS Secrets Manager, Yandex Lockbox, Kubernetes Secret, GitHub Actions secret и подобные);
  • отдельное значение на каждое окружение: рабочее, тестовое, локальная разработка;
  • отдельное значение на каждый сервис, если их несколько — тогда отзыв одного не останавливает остальные.

Нельзя:

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

Как ключ устроен со стороны ActionPulse: значение имеет вид pp_<тип>_<префикс>_<секрет>, в базе хранится только хеш полного значения и безопасный 8-символьный префикс. Полное значение показывается один раз, при выпуске или ротации, и восстановить его позже нельзя. Префикс не секретен — именно его безопасно называть в обращении в поддержку.

Ещё две детали, которые полезны при отладке:

  • сообщение при 401 одинаково для всех причин — отсутствует, не разбирается, отозван, истёк, передан недопустимым способом. По ответу нельзя подбирать ключи;
  • если заголовок Authorization в запросе присутствует, но собран неверно, запрос получает 401 и не «проваливается» на менее доверенный способ передачи.

Ротация и порядок действий при подозрении на утечку

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

Если под подозрением серверный ключ

Порядок, который не теряет события:

  1. Выпустите новый серверный ключ (роль «Админ» или «Владелец», Настройки → Проект → Credentials проекта). Именно выпуск, а не ротацию: старый ключ пока продолжает работать.
  2. Положите новое значение в менеджер секретов и раскатите на все процессы, которые отправляют события. Не забудьте фоновые обработчики и cron.
  3. Убедитесь, что события идут новым ключом: ответ 202 с пустым rejected, свежие события на экране проверки интеграции.
  4. Отзовите старый ключ. Отзыв необратим и действует сразу; если внутренний канал мгновенного оповещения недоступен, отказ наступает по истечении кэша — в пределах пяти минут.
  5. Смените всё, что лежало рядом. Если ключ утёк вместе с файлом окружения, считайте скомпрометированным весь файл, а не одну строку.
  6. Разберитесь с последствиями в данных. Проверьте за подозрительный период подтверждённые бизнес-события и связывания идентификаторов: подделанные оплаты и заказы искажают выручку, а склейка идентичности не отменяется сама.
  7. Зафиксируйте инцидент у себя. ActionPulse не определяет утечку и не присылает о ней уведомлений; в списке ключей видно только префикс, статус и дату последнего использования.

Если под подозрением браузерный токен

Браузерный токен публичен, поэтому «утечка» здесь — не событие безопасности, а злоупотребление: кто-то использует ваш токен со своей страницы или из скрипта. Признаки видны в данных: необъяснимый рост числа событий, события с чужих адресов страниц в списке событий, мусорные значения свойств.

  1. Проверьте, что список origin токена действительно узкий и не содержит лишних адресов.
  2. Выпустите новый токен с точным списком origin и минимальным списком своих имён событий. Добавить домен или имя события в существующий токен нельзя — ротация профиль не меняет.
  3. Замените значение в разметке или в вызове инициализации и раскатите.
  4. Проверьте приём и отзовите старый токен.

Чего продукт не гарантирует

Прямой список, чтобы не пришлось выяснять это в инциденте.

  • Не доказывает, что браузерное событие отправил ваш посетитель. Публичный токен по определению может использовать кто угодно. Достоверность даёт только серверный ключ.
  • Не проверяет origin у не-браузерных клиентов. Заголовок можно подделать; список origin — гигиена, а не аутентификация.
  • Не обнаруживает утечку ключа. Нет ни поиска ключей в публичном интернете, ни оповещений об аномалиях по конкретному ключу.
  • Не показывает полное значение ключа повторно. Потерянное значение заменяется только выпуском нового.
  • Не даёт окна, когда работают старое и новое значения при ротации.
  • Не откатывает данные. Ни подделанные события, ни выполненные связывания идентификаторов не отменяются автоматически.
  • Не отвечает за секрет на вашей стороне. Ключ в репозитории, в логе CI или в браузерном бандле — это ваша граница, и никакая проверка на приёме её не восстановит.

Что проверить

  • Поиск по префиксу серверного ключа во всём фронтенде, в контейнере тег-менеджера и в репозитории не даёт совпадений.
  • Серверный ключ читается из окружения, а окружение — из менеджера секретов; значение не появляется в логах, в сообщениях об ошибках и в трассировках.
  • У браузерного токена в списке origin только рабочие адреса, а в списке имён событий — только те, что реально вызывает фронтенд.
  • Подтверждённые бизнес-факты отправляются исключительно с бэкенда.
  • В календаре есть напоминание о сроке действия браузерного токена: 90 дней проходят незаметно, а истечение выглядит как внезапный 401.
  • Порядок действий при утечке записан у вас в runbook: кто выпускает ключ, кто раскатывает, кто отзывает старый и кто проверяет данные за период.
  • Ключи выпускают только те, кому это нужно: выпуск, ротация и отзыв доступны роли «Админ» и «Владелец» — см. роли и права.