Перейти к содержимому
Безопасность

Что защищает арендатора, сказано прямо.

Никаких сертификаций, которых у нас нет, и никаких прилагательных вместо средств контроля. Вот что обеспечивается, где обеспечивается и чего не хватает.

Шестнадцать рабочих пространств и ничего между нимиРешётка ячеек рабочих пространств четыре на четыре; в каждой — операторы, ключи, версии конфигурации и журнал аудита. Промежутки между ними прочерчены сплошными линиями, и нигде на чертеже ни один путь их не пересекает. Одна ячейка раскрыта в тройном масштабе: репозиторий в границах рабочего пространства, ключ API и секрет вебхука, каждый со своим key id, и журнал аудита только на добавление, обеспеченный триггером базы данных.Рабочие пространстваИзоляция структурна01020304050607080910111213141516Одно рабочее пространствоworkspace_idкаждый запрос в слое данных требует егоapi_key(kid)ротируется в перекрывающемся окнеwebhook_secret(kid)ротируется так жеaudit_logтолько добавление — обеспечивается триггером БДНи один путь не пересекает разрыв — в этом отсутствии и есть смыслКаждый запрос в слое данных принимает рабочее пространство. Нет вызова, который может его опустить.
Шестнадцать рабочих пространств. Между двумя ячейками не нарисовано ничего, потому что между ними ничего и нельзя записать.
Мультиарендность

Изоляция структурна, а не поднадзорна.

Каждый запрос к данным рабочего пространства несёт его id в условии. Не существует пути доступа без области видимости, который можно было бы забыть применить.

Этот дизайн убирает самый обыкновенный отказ: корректная система, в которой один новый эндпойнт, написанный в спешке, теряет условие WHERE. Если слой данных не отвечает на вопрос, в котором не назван рабочий пространство, такой эндпойнт не компилируется в утечку — он вообще не компилируется.

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

internal/tenancy · migrations/0002

Запись

Журнал аудита защищён от изменений в базе данных, а не в приложении.

Триггер выбрасывает ошибку при любом обновлении или удалении. Приложение, решившее не переписывать историю, — это политика; база данных, отказывающаяся её переписывать, — это средство контроля.

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

internal/tenancy · migrations/0002 audit_log_immutable()

Учётные данные

Ключи ротируются без простоя. Вход устойчив к фишингу.

Ротация, которой нужно окно техработ, — это ротация, которая не происходит.

Два объёма и плоскость между нимиДва изометрических объёма стоят на двух отдельных участках размеченной земли, между ними — вертикальная плоскость, видимая с ребра. Левый объём принадлежит оператору и разделён на четыре отсека: Stripe, NOWPayments, банк и кошелёк. Правый объём принадлежит платформе и разделён на реестр, прогоны, заявки и исходящую очередь. Плоскость между ними пересекают четыре линии, подписанные прямо на ней: событие, инструкция, квитанция и команда. Ни одна линия не несёт денег, и в правом объёме нет отсека, способного хранить баланс.Два объёма · одна плоскость · без двериSTRIPEКнигаNOWPAYMENTSПрогоныБанкЗаявкиКошелёкИсходящиеОператорXEXГраница храненияСобытиеИнструкцияКвитанцияКомандаНи один объём не пересекаетДеньги лежат в отсеках оператора и никогда не копируются к нам.Плоскость пересекают четыре вещи. Ни одна из них не баланс и не ключ.
Два объёма и одна плоскость между ними. Двери в ней нет.
  1. 01

    Идентификаторы ключей с пересекающимся сроком действия

    Ключи API и ключи подписи вебхуков каждого рабочего пространства несут идентификатор ключа. Во время ротации действительны оба, поэтому вы переключаетесь по собственному графику и отзываете старый после.

    internal/tenancy · internal/outbox/sign.go

  2. 02

    Passkey и для участников, и для операторов

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

    internal/passkey

  3. 03

    Двухфакторка через приложение, хранится правильно

    Секрет TOTP нельзя захешировать — проверка заново вычисляет из него коды, — поэтому секреты участников запечатываются AES-256-GCM под подключом, отдельным для этого назначения, а не лежат открытым текстом. Счётчик принятого кода записывается обратно, поэтому код нельзя переиспользовать внутри его окна.

    internal/membertotp · internal/secretbox

  4. 04

    Хешируем, где можем; запечатываем, где нельзя

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

    internal/secretbox

Исходящий трафик

Ничто не утекает через URL, который настроили вы.

Адрес назначения вебхука по своей природе — ввод, на который влияет атакующий: это URL, который набирает арендатор, а платформа затем обращается к нему изнутри собственной сети.

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

internal/outbox/ssrf.go · internal/outbox/dispatcher.go

Хранение средств

Мы не кастодиан, и код отказывается им становиться.

Никаких средств участников. Никаких приватных ключей. Никаких данных платформы внутри платёжного канала арендатора.

Участники платят вам

Каждый канал настроен собственными учётными данными арендатора и на его собственных счетах.

internal/rails

Конфигурация отвергает наши

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

internal/rails

Выплата поручает, но не перемещает

Запрос резервирует ровно те строки книги, что нужно, и выпускает поручение. Исполнение — ваше, и деньги, против которых оно исполняется, тоже ваши.

internal/claims

Личность

Проверка без хранения документов.

Сессия провайдера идёт на вашем собственном аккаунте. Возвращается решение и необратимый идентификатор.

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

internal/verification

Чего мы не заявляем

Чего мы не заявляем.

Если вы заполняете опросник по поставщику, вот строки, по которым мы не пройдём. Лучше здесь, чем на четвёртый месяц.

У нас нет отчёта SOC 2. Независимое тестирование на проникновение не проводилось. Мы не публикуем обязательств по доступности сервиса, потому что здесь пока нечем её измерить. XEX даёт вам средства контроля и записи; он не даёт лицензии и не является юридической консультацией. Законен ли ваш план вознаграждений в стране, где вы его продвигаете, устанавливаете вы сами — и условия платформы этого от вас требуют.
Сообщение об уязвимости
Программы вознаграждения за ошибки сегодня нет, и за отчёт мы не платим. Сообщите об уязвимости через канал безопасности на странице контактов; мы подтвердим получение и расскажем, что делаем. Пожалуйста, не тестируйте на рабочем пространстве действующего арендатора — возьмите бесплатную песочницу, она и есть весь продукт, и тестируйте там.

See /contact#security

Дальше

Пришлите опросник или возьмите песочницу и посмотрите сами.

И то и другое нормально. Один из вариантов быстрее.