© 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-песочница не обслуживается (вместо неё можно выдавать именованные песочницы через POST /sandboxes), изоляция песочниц (см. раздел «Изоляция песочниц» ниже), rate limit, TTL состояния, egress-фильтр, мониторинг |
| CI-прогоны | Отдельный инстанс на job, MOCK_STATE_FILE во временной директории |
Контейнерный харденинг. Все три 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.
Доступ к управляющей поверхности:
/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_TOKEN
default-песочницу не обслуживают (отказ песочницы not-found → 404).
Платёжный контракт провайдера (/v2/*, /e2c/*, /possystem/*
кроме управляющих) управляющих токенов не требует. Перед публикацией стенда
задайте оба управляющих токена и MOCK_SANDBOX_TOKEN.
Сессия консоли. Управляющие токены не возвращаются в 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-токены отклоняются. Cookie
читается с декодированием (%3D → =) и разделяется по первому = — токены с
=, ;, & работают через форму входа.
MOCK_TENANCY управляет режимом аренды: namespaces (дефолт self-hosted) —
изолированные пространства имён для параллельных CI-воркеров; owned
(включается MOCK_HOSTED=1 или явным MOCK_TENANCY=1) — hosted tenancy с
owner-token; off — одна общая песочница. В owned:
- контекст песочницы разрешается на каждый запрос (request-local), состояние, креды, очереди и персистентность не переключаются глобально;
POST /sandboxesвыдаётownerTokenодин раз; знание id без него доступа не даёт — бизнес- и управляющие маршруты чужой песочницы отвечают 401, чужой снимок состояния прочитать нельзя; owner-токены хранятся только хэшем;- owner-token читается в порядке
X-Sandbox-Token→X-Owner-Token→ cookiemock_sandbox_token→Authorization: Bearer→?sandbox_token=; явный заголовок приоритетнее Bearer, чтобы бизнес-авторизация адаптера (Первый ОФД — токен очереди, ВТБ — OAuthaccess_token) не затеняла токен владельца. Bearer-fallback для клиентов без явных заголовков сохранён; пустой источник считается отсутствующим. Cookie читается с декодированием, а?sandbox_token=принимается только в non-hostednamespaces(с WARN об устаревании): вownedи в hosted (любая аренда) он отклоняется fail-closed — 401 с подсказкой передать токен заголовком или cookie; - оператор стенда (
MOCK_OPERATOR_TOKEN) распознаётся поAuthorization: Bearer/X-Operator-Token/ query независимо от приоритета owner-источников, поэтому смена порядка операторский путь не затронула; - песочницы живут по TTL и ограничены количеством, квотой документов и темпом
создания (
MOCK_SANDBOX_TTL_MS,MOCK_SANDBOX_MAX,MOCK_SANDBOX_MAX_DOCUMENTS,MOCK_SANDBOX_CREATE_LIMIT), терминальные диагностические записи подчищаются retention-ом (MOCK_SANDBOX_RETENTION_MS, по умолчанию выключен), реестр пишется атомарно,defaultне может быть подменён чужой песочницей; - в hosted (
MOCK_HOSTED=1) безMOCK_SANDBOX_TOKENdefault-песочница не обслуживается: бизнес- и form-маршруты без селектора и явныйsandbox=defaultполучают отказ песочницы (404not-foundс подсказкой задать токен), стартовый лог сообщаетdefault=closed. Публичная инфраструктура (/health,/openapi.json, реестр/sandboxes) и именованные песочницы (POST /sandboxes- owner-token) работают: стенд можно проверить и провизионить. Управляющая
поверхность (
/admin*,/mock/*) намеренно не перекрывается этим правилом — у неё собственный fail-closed гейт (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
обязан быть идемпотентным).
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 и ограничения.
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.
Журнал /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включена по умолчанию в адаптерах и в ядре) блокирует приватные и служебные IP-литералы (IPv4/IPv6, включаяff00::/8, TEST-NET-1/2/3,2001:db8::/32и64:ff9b:1::/48) и имена, которые в них резолвятся, запрещает схемы кроме http/https и привязывает проверенный IP-адрес к сокету доставки (защита от DNS TOCTOU / rebinding). Для полной инфраструктурной изоляции на публичном стенде дополнительно используйте сетевой egress-фильтр. - Редиректы при доставке вебхуков не отслеживаются (клиент их не выполняет) — дополнительная защита от redirect-to-private не требуется.
- Мок принимает любые URL вебхуков — при публичном размещении это вектор SSRF/DoS, если защиты не включены.
- Управляющие токены сравниваются constant-time, но передаются по HTTP: на публичном стенде ставьте его за TLS-прокси и не логируйте query-параметры.
Не создавайте публичный issue для уязвимостей, позволяющих выйти за периметр, получить чужое состояние или выполнить код. Напишите мейнтейнерам приватно (контакт в профиле организации, либо через GitHub Security Advisories репозитория) и опишите: версия/коммит, шаги воспроизведения, влияние.
Мы подтвердим получение в течение 3 рабочих дней и опубликуем исправление; по желанию укажем автора в релиз-нотах.
MOCK_SANDBOX_MAX_STATE_BYTES ограничивает JSON одной песочницы,
MOCK_SANDBOX_GLOBAL_MAX_STATE_BYTES — сумму JSON всех песочниц рантайма
(64 МиБ по умолчанию, включая default). 0 выключает соответствующий лимит;
не выключайте глобальный бюджет на недоверенном публичном стенде.
Бюджет не является пределом RSS: объекты, снимки отката и volatile-дочернее
состояние расходуют дополнительную память. Контейнерный mem_limit остаётся
необходимым; подбирайте JSON-бюджет по измеренному RSS. Старые файлы состояния
восстанавливаются без потери данных, но следующие записи сверх бюджета
блокируются до уменьшения состояния. Подробности —
квоты ядра.