Appearance
© 2026 ООО «Аксиома» ИНН:7842223709 · mock-services · Apache-2.0 SPDX-License-Identifier: Apache-2.0
Безопасность
Что это за проект
mock-services — тестовый стенд. Он эмулирует банк-эквайринг и облачную кассу/ОФД, чтобы интеграторы тестировали свои платёжные и фискальные потоки. По построению:
- все креды в проекте фейковые (
TBANKMOCKTEST/mockpass123,loviitest/mockpass123и т.п.); - платежи проходят только по тестовым картам из списка мока;
- мок не является платёжным или фискальным сервисом и не должен получать реальные ключи, реальные карточные данные и персональные данные покупателей.
Что делать, если вы разворачиваете стенд
| Сценарий | Обязательные меры |
|---|---|
| Локально / внутри периметра | Ничего дополнительно; не публикуйте порты в интернет |
| Доступен команде по сети | MOCK_OPERATOR_TOKEN (управляющие /mock/*, смены/ФН/ОФД, чтение спора) и MOCK_ADMIN_TOKEN (админка); включите MOCK_BLOCK_PRIVATE_WEBHOOKS=1 |
| Публичный демо-стенд | MOCK_HOSTED=1 (fail-closed управляющей поверхности и SSRF-политика по умолчанию), операторский и админский токены, MOCK_SANDBOX_TOKEN — иначе default-песочница не обслуживается (MS-12; вместо неё можно выдавать именованные песочницы через POST /sandboxes), изоляция песочниц (см. docs/rfc/RFC-001-multi-tenant-sandboxes.md), rate limit, TTL состояния, egress-фильтр, мониторинг |
| CI-прогоны | Отдельный инстанс на job, MOCK_STATE_FILE во временной директории |
Контейнерный харденинг (MS-04). Все три compose-файла запускают сервисы с init: true, read_only: true, tmpfs: ["/tmp"], cap_drop: ["ALL"], security_opt: ["no-new-privileges:true"] и pids_limit: 256: запись возможна только в volume /data (состояние) и tmpfs /tmp, capabilities не нужны (порты 3100+ непривилегированные, runtime-пользователь UID 10001). Запуская образ своими командами docker run, повторяйте те же флаги. По SIGTERM/SIGINT (в том числе docker stop) сервис сбрасывает отложенные записи состояния и выходит с кодом 0 — см. раздел «Контейнерный харденинг и graceful shutdown» в README.md.
Доступ к управляющей поверхности (F-07):
/mock/*(sandboxes,reset,state,time,scenarios,verify-merchant), смены ККТ, ресурс ФН, режим/очередь ОФД и чтение спораGET /v2/Dispute/{id}—MOCK_OPERATOR_TOKEN(принимается вAuthorization: Bearer,X-Operator-Tokenили?operator_token=; fallback —MOCK_ADMIN_TOKEN);/admin*—MOCK_ADMIN_TOKEN.
Пока токены не заданы, локальный стенд открыт (осознанный DX-дефолт). При MOCK_HOSTED=1 управляющие маршруты без настроенного секрета отвечают 401 — fail-closed; бизнес- и form-маршруты в hosted без MOCK_SANDBOX_TOKENdefault-песочницу не обслуживают (отказ песочницы not-found → 404, MS-12). Платёжный контракт провайдера (/v2/*, /e2c/*, /possystem/* кроме управляющих) управляющих токенов не требует. Перед публикацией стенда задайте оба управляющих токена и MOCK_SANDBOX_TOKEN.
Сессия консоли (Ф-1, #121). Управляющие токены не возвращаются в URL и HTML: POST /admin/login и POST /admin/action отвечают редиректом без токенов в Location (в ?sandbox= остаётся только селектор песочницы), ссылки и формы консоли токенов не содержат, а принятый из query-строки токен в non-hosted переносится в HttpOnly-cookie (mock_admin_token, mock_operator_token, mock_sandbox_token; SameSite=Strict, Path=/) — дальнейшая навигация и формы работают по cookie. В hosted query-токены отклоняются (MS-06/Ф-3). Cookie читается с декодированием (%3D → =) и разделяется по первому = — токены с =, ;, & работают через форму входа (Ф-2).
Изоляция песочниц (RFC-001, A-21)
MOCK_TENANCY управляет режимом аренды: namespaces (дефолт self-hosted) — изолированные пространства имён для параллельных CI-воркеров; owned (включается MOCK_HOSTED=1 или явным MOCK_TENANCY=1) — hosted tenancy с owner-token; off — одна общая песочница. В owned:
- контекст песочницы разрешается на каждый запрос (request-local), состояние, креды, очереди и персистентность не переключаются глобально (F-11);
POST /sandboxesвыдаётownerTokenодин раз; знание id без него доступа не даёт — бизнес- и управляющие маршруты чужой песочницы отвечают 401, чужой снимок состояния прочитать нельзя (F-12); owner-токены хранятся только хэшем;- owner-token читается в порядке
X-Sandbox-Token→X-Owner-Token→ cookiemock_sandbox_token→Authorization: Bearer→?sandbox_token=; явный заголовок приоритетнее Bearer, чтобы бизнес-авторизация адаптера (Первый ОФД — токен очереди, ВТБ — OAuthaccess_token) не затеняла токен владельца (#98). Bearer-fallback для клиентов без явных заголовков сохранён; пустой источник считается отсутствующим. Cookie читается с декодированием (Ф-2, #121), а?sandbox_token=принимается только в non-hostednamespaces(с WARN об устаревании): вownedи в hosted (любая аренда) он отклоняется fail-closed — 401 с подсказкой передать токен заголовком или cookie (Ф-3, #121); - оператор стенда (
MOCK_OPERATOR_TOKEN) распознаётся поAuthorization: Bearer/X-Operator-Token/ query независимо от приоритета owner-источников, поэтому смена порядка (#98) операторский путь не затронула; - песочницы живут по TTL и ограничены количеством, квотой документов и темпом создания (
MOCK_SANDBOX_TTL_MS,MOCK_SANDBOX_MAX,MOCK_SANDBOX_MAX_DOCUMENTS,MOCK_SANDBOX_CREATE_LIMIT), терминальные диагностические записи подчищаются retention-ом (MOCK_SANDBOX_RETENTION_MS, по умолчанию выключен), реестр пишется атомарно,defaultне может быть подменён чужой песочницей (F-14/F-15); - в hosted (
MOCK_HOSTED=1) безMOCK_SANDBOX_TOKENdefault-песочница не обслуживается (MS-12, #121): бизнес- и form-маршруты без селектора и явныйsandbox=defaultполучают отказ песочницы (404not-foundс подсказкой MS-12), стартовый лог сообщаетdefault=closed. Публичная инфраструктура (/health,/openapi.json, реестр/sandboxes) и именованные песочницы (POST /sandboxes- owner-token) работают: стенд можно проверить и провизионить. Управляющая поверхность (
/admin*,/mock/*) намеренно не перекрывается MS-12 — у неё собственный fail-closed гейт MS-06 (401 без админского/операторского токена), и владелец может «раскрыть» default, задавMOCK_SANDBOX_TOKEN;
- owner-token) работают: стенд можно проверить и провизионить. Управляющая поверхность (
- в self-hosted (без
MOCK_HOSTED)defaultоткрыта по DX-контракту, пока не заданMOCK_SANDBOX_TOKEN; вownedбез токена стартовый лог пишетdefault=open (owned без MOCK_SANDBOX_TOKEN — задайте токен, чтобы закрыть).
Сценарные правила (/mock/scenarios) и журнал запросов остаются в области стенда и защищены операторским/админским токеном; не выдавайте их арендаторам.
Креды адаптеров задаются переменными MOCK_<ПРОВАЙДЕР>_* (для Альфа-Банка — MOCK_ALFA_USERNAME, MOCK_ALFA_PASSWORD, MOCK_ALFA_CALLBACK_SECRET, MOCK_ALFA_TERMINAL_ID; для Сбера — MOCK_SBER_USERNAME и MOCK_SBER_PASSWORD; для ВТБ — MOCK_VTB_CLIENT_ID и MOCK_VTB_CLIENT_SECRET; для Первого ОФД — MOCK_FIRST_OFD_LOGIN и MOCK_FIRST_OFD_PASS; для Такском — MOCK_TAXCOM_LOGIN, MOCK_TAXCOM_PASSWORD и MOCK_TAXCOM_INTEGRATOR_ID). Дефолты в образе — синтетические; реальные тестовые креды банка зашивать в конфиг стенда нельзя. В частности, SBERMOCKTEST / mockpass123 — локальные синтетические креды, не доступ к Ecom, vtbmocktest / mockpass123 — синтетические креды ВТБ-мока, не реквизиты банка, а firstofdmocktest + синтетический UUID-ключ — креды first-ofd-mock, не доступ к тестовому контуру OKassa (реальные login/API-ключ Первого ОФД выдаются в тестовом ЛК и в конфиг стенда не попадают), а taxcommocktest / mockpass123 / TAXCOMMOCKTEST — синтетические креды taxcom-mock, не реквизиты Такском (публичных тестовых login/password/Integrator-ID провайдер не публикует, их выдаёт техподдержка/кабинет). Дефолты Такском намеренно печатаются в стартовом логе стенда с пометкой «не реквизиты Такском»; переопределённые через env значения заменяются пометкой «задан через env, скрыт». Наружу (HTTP-ответы, hint, stdout, /admin/journal) фактические env-креды не печатаются: при заданной переменной вместо секрета выводится пометка «задан через env, скрыт».
Альфа callback — исходящий GET с query-параметрами, Сбер Ecom — unsigned JSON POST без checksum (target из dynamicCallbackUrl или MOCK_SBER_CALLBACK_URL), ВТБ — unsigned JSON POST {"type":"PAYMENT|REFUND","object":{…}} без подписи (ACK-код и расписание ретраев в публичных срезах не зафиксированы [VERIFY]; расхождение Status/status сохраняется как есть; target из MOCK_VTB_CALLBACK_URL или callbackUrl запроса), Первый ОФД — unsigned JSON POST результата заявки без подписи: публичный срез не закрепляет ни envelope, ни подпись, ни ACK, ни расписание повторов [VERIFY], поэтому мок не выдумывает HMAC/RSA, считает доставкой любой 2xx [FORK] и берёт target из requestMetadata.callbackUrl или MOCK_FIRST_OFD_CALLBACK_URL. Все используют MOCK_BLOCK_PRIVATE_WEBHOOKS (при MOCK_HOSTED=1 включена по умолчанию); SSRF-отказ терминален — запись помечается dumped и не крутит retry-цикл. Callback нельзя считать доказательством оплаты (соответственно — фискализации): merchant должен сверить статус через getOrderStatusExtended.do (Сбер) / GET /orders/{orderId} (ВТБ) / GET /api/external/queue/v1/status/{requestId} (Первый ОФД, приёмник callback обязан быть идемпотентным).
Контур.ОФД: read-only данные и два секрета (#97)
kontur-ofd-mock (:3108) не имеет write API или callback: только чтение фискальных fixtures. MOCK_KONTUR_OFD_LOGIN, MOCK_KONTUR_OFD_PASSWORD, MOCK_KONTUR_OFD_API_KEY задаются через env без статических дефолтов; в owned-песочнице генерируются случайные mock-only креды. Не передавайте реальные провайдерские реквизиты, ФД и ПДн в мок. Пароль передаётся текстом UTF-8: снаружи используйте TLS. Sid и API key независимы; их нельзя считать взаимозаменяемыми или использовать как owner-token.
Sid живёт MOCK_KONTUR_OFD_SID_TTL_SECS (по умолчанию 2592000) на виртуальных часах. Reset/state import/rotation отзывают сессии. В hosted к двум заголовкам провайдера добавляются X-Mock-Sandbox и X-Sandbox-Token; чужие owner-token и Sid не дают доступа. Control fail-closed по общей политике ядра. Без hosted control открыт локально: не публикуйте порт без access-control.
Журнал не записывает auth body, query login и исходные headers; экспорт /mock/state исключает Sid, /sandboxes/me не раскрывает креды повторно. Persist files всё равно содержат mock-сессии и креды: защищайте volume/backup. MOCK_KONTUR_OFD_PUBLIC_BASE_URL задаёт только provisioning URL, не callback; входящих API записи и исходящих webhook-запросов у сервиса нет. Контракт, env и ограничения.
Такском: read-only кабинет/ОФД и маркер на 5 минут (#112)
taxcom-mock (:3109) читает уже принятые ОФД фискальные документы: входящего write API и исходящих запросов у сервиса нет (в публичном срезе v2.17 нет ни создания чека, ни callback/webhook), поэтому очередь доставки ядра и SSRF-политика к адаптеру не подключаются, а неизвестный /API/... даёт 404 с подсказкой. Три секрета — MOCK_TAXCOM_LOGIN, MOCK_TAXCOM_PASSWORD и MOCK_TAXCOM_INTEGRATOR_ID — задаются env; production-реквизиты Такском в код, compose и логи не попадают. Реальные ФД и ПДн в мок не передавайте: fixtures синтетические, а state-файлы лежат на volume.
Маркер Session-Token живёт MOCK_TAXCOM_SESSION_TTL_SECS (по умолчанию 300 с, sliding от последнего использования) на виртуальных часах; просроченный маркер даёт 401 и удаляется. [FORK] Повторный Login выдаёт новый маркер, а прежний действует до своего TTL — то есть у одного пользователя может быть несколько живых маркеров; срез формулирует иначе, расхождение зафиксировано в гайде и манифесте. Reset песочницы, импорт /mock/state и ротация кредов отзывают маркеры; экспорт /mock/state их не раскрывает, /sandboxes/me не раскрывает креды повторно.
Журнал не пишет тело Login (пароль), Session-Token, Integrator-ID и исходные заголовки. В hosted к заголовкам провайдера добавляются X-Mock-Sandbox и X-Sandbox-Token; чужие owner-token и маркеры доступа не дают, control-поверхность fail-closed по общей политике ядра. Без hosted control открыт локально: не публикуйте :3109 без access-control и TLS.
Что стенд хранит и показывает (F-09, F-10)
Журнал /admin/journal, HTML-консоль /admin, снимок /mock/state, файл MOCK_STATE_FILE и логи процесса проходят редакцию (packages/core: redactSecrets/redactRawBody):
- Секреты не сохраняются. Ключи-секреты (
Authorization,Cookie,token,pass/password,CVV,CardData,signature,login) заменяются на[REDACTED]рекурсивно — в теле запроса, в сыром payload, вcurl-сниппете и в логах. Заголовки запроса попадают в журнал только через allowlist (Content-Type,Content-Length,User-Agent,X-Mock-*):Authorizationс Basic/Bearer-кредами в журнал не попадает вовсе. - Карточные данные минимизируются. Выплата
/e2c/v2/Initне хранит PAN,sber-mockпринимает в карточной форме только официальные синтетические test fixtures: форма не журналируется, заказ/state содержат только маскуfirst6**last4;vtb-mockна странице оплаты принимает только синтетические [FORK]-мок-карты (официальная тестовая карта ВТБ истекла), PAN не сохраняется и в журнале/state не попадает. Реальные карточные реквизиты и ПДн недопустимы. Снимки состояния, созданные до соответствующих версий, вычищаются при загрузке. Любая строка журнала дополнительно сканируется на PAN (13–19 цифр, префикс платёжной системы, контрольная сумма Luhn). - PII маскируется. ИНН, телефон, e-mail и ФИО в журнале показываются частично (
7801******,***4567,iv***@example.com,Ив***) — журнал остаётся пригодным для отладки. - Размер записи ограничен. Путь, значения полей, сырое тело и
curl-сниппет имеют предельную длину, одна запись журнала не превышает 8 КБ.
Редакция односторонняя: восстановить исходные реквизиты из журнала нельзя. В curl-сниппете вместо секретов стоят [REDACTED] и YOUR_TOKEN — при воспроизведении подставьте свои значения. Это не отменяет базового правила: стенд не должен получать реальные ключи и реальные карточные данные.
Известные ограничения защиты
- Проверка исходящих вебхуков (
MOCK_BLOCK_PRIVATE_WEBHOOKS, приMOCK_HOSTED=1включена по умолчанию во всех семи сервисах и в ядре — #51 PF-01, PA-58) блокирует приватные и служебные IP-литералы (IPv4/IPv6, включаяff00::/8, TEST-NET-1/2/3,2001:db8::/32и64:ff9b:1::/48— PA-26) и имена, которые в них резолвятся, запрещает схемы кроме http/https и привязывает проверенный IP-адрес к сокету доставки (защита от DNS TOCTOU / rebinding — §7.3, PG-11). Для полной инфраструктурной изоляции на публичном стенде дополнительно используйте сетевой egress-фильтр. - Редиректы при доставке вебхуков не отслеживаются (клиент их не выполняет) — дополнительная защита от redirect-to-private не требуется.
- Мок принимает любые URL вебхуков — при публичном размещении это вектор SSRF/DoS, если защиты не включены.
- Управляющие токены сравниваются constant-time, но передаются по HTTP: на публичном стенде ставьте его за TLS-прокси и не логируйте query-параметры.
Как сообщить об уязвимости
Не создавайте публичный issue для уязвимостей, позволяющих выйти за периметр, получить чужое состояние или выполнить код. Напишите мейнтейнерам приватно (контакт в профиле организации, либо через GitHub Security Advisories репозитория) и опишите: версия/коммит, шаги воспроизведения, влияние.
Мы подтвердим получение в течение 3 рабочих дней и опубликуем исправление; по желанию укажем автора в релиз-нотах.
Байтовый бюджет состояния (PD-02)
MOCK_SANDBOX_MAX_STATE_BYTES ограничивает JSON одной песочницы, MOCK_SANDBOX_GLOBAL_MAX_STATE_BYTES — сумму JSON всех песочниц рантайма (64 МиБ по умолчанию, включая default). 0 выключает соответствующий лимит; не выключайте глобальный бюджет на недоверенном публичном стенде. Бюджет не является пределом RSS: объекты, снимки отката и volatile-дочернее состояние расходуют дополнительную память. Контейнерный mem_limit остаётся необходимым; подбирайте JSON-бюджет по измеренному RSS. Старые файлы состояния восстанавливаются без потери данных, но следующие записи сверх бюджета блокируются до уменьшения состояния. Подробности — квоты ядра.