UDENTIVA Умная платформа управления стоматологией
БЕЗОПАСНОСТЬ

Модель безопасности API

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

Изоляция на уровне строк Идемпотентная запись Параметризованный запрос Изоляция loopback
Изоляция данных

Изоляция на уровне строк — fail-closed

В каждой таблице с областью действия арендатора включён режим PostgreSQL Row-Level Security FORCE; политика привязывает каждую строку к условию «tenant_id = current_setting('app.current_tenant')».

Запрос, пришедший без установленного tenant context, возвращает ноль строк — это даже не ошибка, а тихо пусто. Увидеть данные одной клиники под сессией другой архитектурно невозможно.

Тест кросс-арендной изоляции обязательно зелёный в непрерывной интеграции (CI); если красный — код не сливается.

Атомарная запись

Транзакционно-локальная идентичность, атомарная операция

Идентификатор арендатора устанавливается В САМОМ НАЧАЛЕ каждой операции записи и действует только в пределах этой транзакции; он не закрепляется за пулом соединений и не просачивается в другие запросы.

Многосущностные операции (счёт + позиция + платёж + списание со склада) атомарны внутри одной транзакции: либо всё записывается вместе, либо ничего. Не возникает незавершённой, несогласованной записи.

Права

Ролевые права (RBAC) обеспечиваются на сервере

Контроль прав выполняется на уровне API; клиенту или фронтенду никогда не доверяют. Скрыть меню в браузере — это только про внешний вид, реальное решение — на сервере.

Мутации ядра (Фаза 1) защищены по ролям администратора, администратора-регистратора и врача. Даже если конечную точку вызовет неуполномоченная роль, запрос отклоняется.

Защита от повтора

Идемпотентная запись — двойной операции нет

Каждый запрос записи несёт Idempotency-Key; блокировка на стороне сервера предотвращает вторую операцию с тем же ключом.

Даже при обрыве сети или повторной отправке того же запроса клиентом второй платёж или счёт не создаётся. Результат всегда единственный и предсказуемый.

Безопасность запросов

SQL injection невозможен — параметризованный запрос

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

Это структурно устраняет целый класс атак SQL injection — опора не на «фильтрацию» или «экранирование», а на сам способ построения запроса.

Сетевая поверхность

Изоляция loopback — ядро закрыто наружу

Основной API прослушивает только 127.0.0.1 (loopback). Попытка привязки к 0.0.0.0 или публичному IP отклоняется ещё при запуске; ядро ни при каких условиях не открывается в интернет.

Единственная публичная поверхность — это тонкий шлюз (BFF): он не хранит бизнес-логику, кроме сессии, рендеринга и CSRF. Браузер никогда не может напрямую увидеть или адресовать основной API.

Идентификация и сессия

Пароль argon2id, opaque ключ сессии

Пароли хешируются с помощью отраслевого стандарта argon2id, устойчивого к атакам по памяти; простое или слабое хеширование не используется.

Ключи сессии — это непрозрачные (opaque) значения длиной 32 байта, сгенерированные криптостойким генератором; они хранятся в Redis и истекают по TTL. В браузер попадает только cookie с флагами HttpOnly + Secure + SameSite; сам ключ никогда не раскрывается JavaScript.

Аудит

Неизменяемый журнал аудита (append-only)

Важные операции записываются в append-only журнал аудита в ТОЙ ЖЕ транзакции, что и запускающая их мутация. Если операция откатывается, запись аудита тоже не пишется — они неразделимы.

Права UPDATE/DELETE у таблицы аудита отозваны; записи невозможно изменить или удалить впоследствии. Прошлое остаётся как есть.

Privacy

Маскирование чувствительных данных

TCKN, пароль, ключ сессии и медицинские данные ни в каком виде не попадают в журналы. Даже в отладочном выводе эти поля маскируются.

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

Целостность денег

Безопасность денег — numeric, обратная запись

Деньги везде хранятся как numeric(14,2); расчёты с деньгами через float не выполняются. Округление происходит в одной точке, на сервере — не возникает смещения копеек или разницы округления.

Отмена и возврат обрабатываются как обратная запись вместо удаления записи. Расчётный баланс не пишется вручную; он всегда выводится из суммы счетов и платежей. Прошлое не искажается.

Передача

Безопасность передачи и браузера

Весь трафик передаётся по HTTPS/TLS, и HSTS включён; браузер устанавливает соединение только в зашифрованном виде.

Строгая политика безопасности контента (CSP) блокирует inline-скрипты; X-Frame-Options DENY предотвращает clickjacking, а X-Content-Type-Options nosniff — подмену типов. Также применяются Referrer-Policy и Permissions-Policy.

Важно

Разделение уровней идентификации + отдельная плоскость управления

Пользователь клиники, портал пациента и оператор платформы — это ОТДЕЛЬНЫЕ уровни идентификации: отдельный заголовок, отдельное пространство сессии. Ключ одного уровня не работает на другом — границы полномочий строго разделены.

Точки доступа платформы и администрирования (подготовка арендаторов, выставление счетов, управление автоматизацией/плагинами, генерация ключей API) НЕ ПРИСУТСТВУЮТ в публичном API.

Эти функции работают в отдельной, внутренней плоскости управления с иным паттерном идентификации (специальный административный ключ); она недоступна и необнаружима извне. Так поверхность атаки заметно уменьшается.

КАК ЭТО РАБОТАЕТ

Одна транзакция, изоляция fail-closed

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

# 1) Контекст арендатора устанавливается В САМОМ НАЧАЛЕ транзакции (только для этой tx)
BEGIN;
  SELECT set_config('app.current_tenant', :session_tenant_id, true);

  # 2) Блокировка Idempotency-Key — со тем же ключом повторная операция не создаётся
  SELECT lock_idempotency(:idempotency_key);

  # 3) Запись нескольких сущностей — все в одной tx атомарно
  INSERT INTO invoices  ...;   # RLS: tenant_id покрывается автоматически
  INSERT INTO payments  ...;
  INSERT INTO audit_log ...;   # append-only, та же tx
COMMIT;

# Если context не установлен, политика RLS не возвращает ни одной строки:
SELECT * FROM patients;  --> 0 строк (не ошибка, тихо пусто)
Независимо проверяемо

Часть безопасности передачи можно проверить извне: сертификат HTTPS/TLS вашего соединения, а также заголовки безопасности ответа, такие как HSTS и политика безопасности контента, вы можете изучить сами через инструменты разработчика браузера. Наши утверждения опираются на архитектурные механизмы контроля, а не на маркетинговые фразы.

Есть вопрос об архитектуре безопасности?
Наша команда с радостью обсудит с вами технические детали и ваши требования к соответствию.
Связаться