КейсыРастИИшка
Разработка под задачу

Изоляция данных ИИ-сервиса: общая платформа и разные компании

Обсудить задачу → Оставить заявку

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

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

Представьте: две компании, «Альфа» и «Бета», работают в одном ИИ-сервисе, у каждой свой аккаунт, свои сотрудники и свои документы. В понедельник инженер «Альфы» выгружает отчёт и обнаруживает в нём строку из базы «Беты». Никто ничего не взламывал — просто фоновая задача взяла список компаний из кеша и обработала всех подряд. Сцена условная, но именно так выглядит класс ошибок, который ищут при проверке изоляции.

Изоляция данных в мультитенантном сервисе — это не один рубильник «включить приватность», а набор мест, где компания-арендатор (тенант) должна быть явно названа и проверена. Общая инфраструктура сама по себе нормальна: одна база, одни очереди, одни модели. Проблема начинается там, где код или конфигурация перестают спрашивать «а чья это строка?» и начинают доверять тому, что пришло снаружи или осталось в памяти с прошлого запроса. Ниже — рабочая методика, как пройти по этим местам и не перепутать аутентификацию с авторизацией тенанта.

Общий маршрут запроса с проверкой компании на каждом слое
Общий маршрут запроса с проверкой компании на каждом слое. Авторская схема 404ai.

Аутентификация отвечает «кто вы», а не «в какой компании»

Самая частая путаница: команда считает, что раз пользователь вошёл по логину и паролю, то и данные он увидит только свои. Это неверно. Успешный вход подтверждает личность, но ничего не говорит о том, к каким компаниям эта личность имеет право и в каком контексте сейчас работает. OWASP в Authentication Cheat Sheet разводит эти слои: аутентификация устанавливает субъекта, а решение о доступе к ресурсу — отдельный шаг. Если сервис после входа просто кладёт user_id в сессию и дальше фильтрует данные по нему, то пользователь, состоящий в двух компаниях, неизбежно увидит смесь.

Поэтому тенант — это отдельный параметр контекста, который живёт рядом с пользователем, а не выводится из него автоматически. В токене или сессии полезно хранить не «я такой-то», а «я такой-то, действую от имени компании X, и вот роли внутри X». Тогда каждый запрос несёт явный ответ на вопрос «в чьём пространстве мы сейчас работаем», и его можно проверить до того, как код полезет в базу.

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

Контекст тенанта как обязательный аргумент, а не глобальная переменная

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

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

Очередь, в которой забыли компанию

Фоновые задания — тихий источник смешения, потому что у них нет живого пользователя, который «случайно» заметит чужие данные. Задача на пересчёт рекомендаций, индексацию документов или отправку сводки ставится в очередь; в сообщении передают идентификатор объекта, но забывают тенант. Воркер берёт объект по id, а id глобально уникален — и обрабатывает данные, не зная, чьи они. Если внутри обработчика есть запрос «дай все связанные записи», он может зацепить соседей.

Лечится это тем, что тенант кладут в само сообщение очереди и проверяют при приёме, а воркер работает только в рамках полученного контекста. Полезно добавить дешёвую страховку: перед обработкой сверить, что объект действительно принадлежит указанной компании, и если нет — отправить сообщение в карантин, а не «починить на лету». Карантин с алертом лучше, чем молчаливая обработка чужого.

Рабочий объект: обязательные поля записи, сообщения и файла
Рабочий объект: обязательные поля записи, сообщения и файла. Авторская схема 404ai.

Кеш, который помнит лишнее

Кеш ускоряет всё, включая утечки. Если ключ кеша — это user_id или document_id без компании, два разных тенанта с одинаковым числовым id получат одну и ту же закешированную запись. Ещё коварнее кеш на уровне процесса: словарь «последние настройки компании» без привязки к тенанту отдаст настройки того, кто обратился первым. Правило простое: тенант входит в ключ кеша всегда, даже когда кажется, что id и так уникален. «Кажется» — плохая защита.

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

Файлы — классика. Документ лежит в объектном хранилище под ключом вроде reports/2024/q3.pdf. Если сервис отдаёт файл по прямой ссылке, не сверяя компанию, достаточно подставить чужой путь. Даже когда путь содержит случайный идентификатор, полагаться на «никто не угадает» — это безопасность через незнание, а не через проверку. OWASP и обзоры мультитенантных архитектур сходятся в том, что доступ к объекту проверяется на каждом обращении, а не один раз при выдаче ссылки.

Практичнее хранить файлы в пространстве, разграниченном по тенанту (отдельный префикс или контейнер на компанию), и выдавать не постоянный адрес, а короткоживущую ссылку, которую формирует сервис после проверки прав. Тогда даже утёкшая ссылка перестаёт работать через несколько минут, а прямая подстановка чужого пути упирается в проверку на стороне выдачи. Обзор подходов к мультитенантности от Microsoft в Multitenant architecture полезен как карта слоёв, где такая проверка должна стоять, — но конкретные механизмы хранения вы выбираете под свою инфраструктуру.

Поиск, который умеет отсекать чужие документы

Полнотекстовый и векторный поиск — место, где смешение особенно заметно: пользователь вводит запрос и получает фрагменты из документов другой компании, потому что индекс общий, а фильтр по тенанту не применён. Microsoft описывает подход security trimming в Security trimming in search: в индекс кладут идентификаторы, по которым потом отсекаются результаты в момент запроса. Идея переносима на любой поисковый движок: тенант — обязательное поле документа в индексе и обязательный фильтр в каждом запросе, а не украшение.

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

Проверка исключений: общие справочники и совместные проекты
Проверка исключений: общие справочники и совместные проекты. Авторская схема 404ai.

Администратор платформы: самый опасный «свой»

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

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

Экспорт и отчёты: где смешение становится видимым

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

Полезное правило: экспорт собирается тем же слоем доступа к данным, что и обычные экраны, а не собственным SQL-запросом. Если без отдельного запроса не обойтись, он проходит тот же тест на «нет тенанта — нет данных». И перед выдачей файла — ещё одна сверка: все строки принадлежат запрошенной компании.

Приёмочные тесты на попытку достать чужие данные
Приёмочные тесты на попытку достать чужие данные. Авторская схема 404ai.

Пользователь, который работает в двух компаниях

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

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

Контекст компании в запросе: что должно быть в каждой строке

Чтобы проверка была не разовой акцией, а свойством системы, полезно зафиксировать форму рабочего объекта — минимальный набор полей, который обязан быть у любой записи, файла, сообщения очереди и документа в индексе. Если этих полей нет, объект считается неполным и не проходит в обработку.

Объект Обязательные поля Где проверяется
Строка в базе tenant_id, object_id, owner_user_id Обёртка доступа к данным
Сообщение очереди tenant_id, job_type, object_id Приём и обработка воркером
Ключ кеша tenant_id + исходный ключ Чтение и запись кеша
Файл в хранилище Префикс/контейнер тенанта, метаданные Выдача ссылки
Документ в индексе tenant_id как фильтруемое поле Каждый поисковый запрос
Строка экспорта tenant_id в каждой строке Сборка и выдача файла

Такая таблица — не бюрократия, а чек-лист для код-ревью и для тестов. Когда форма объекта одинакова везде, забыть тенант становится труднее: он либо есть, либо объект явно неполон.

Контекст компании в запросе на экране сотрудника

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

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

Практикум: путь одного документа по системе
Практикум: путь одного документа по системе. Авторская схема 404ai.

Контекст компании в запросе: исключения и проверка условий

Реальные системы редко укладываются в «всё строго по тенанту». Есть сценарии, где данные законно пересекаются, и их надо назвать заранее, а не обнаруживать в проде.

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

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

Тесты, которые ловят смешение раньше клиента

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

Тест Что подставляем Ожидаемый результат
Прямой доступ к чужому объекту id компании B Отказ, запись в журнал
Поиск с фильтром соседа запрос от A, документы B Пусто, без фрагментов B
Ссылка на чужой файл путь компании B Отказ выдачи
Очередь без тенанта сообщение без tenant_id Карантин, алерт
Кеш с общим ключом одинаковый id у A и B Разные записи
Экспорт от A данные A и B в выборке Только строки A

Отдельно стоит проверить поведение при переключении тенанта: после смены компании старые ссылки и открытые вкладки не должны отдавать прежние данные. И — журнал: каждое обращение к чужим данным, даже разрешённое для поддержки, должно оставлять след, по которому потом можно восстановить, кто и зачем смотрел.

Практикум: пройти путь одного документа

Возьмите один условный документ компании «Альфа» — скажем, коммерческое предложение — и проследите его путь по системе, отмечая на каждом шаге, где назван тенант. Документ создан сотрудником: есть ли tenant_id в записи? Попал в очередь на индексацию: передан ли тенант в сообщении? Залёг в кеш списка «последние документы»: входит ли компания в ключ? Проиндексирован: есть ли тенант как фильтруемое поле? Открыт по ссылке коллегой: проверена ли компания при выдаче? Выгружен в отчёт: прошли ли строки через общий слой доступа? Отправлен внешнему партнёру: не осталась ли постоянная ссылка, которая переживёт смену прав?

Если на каком-то шаге тенант не назван — это и есть место для исправления. Пройдите так по двум-трём типам объектов (документ, сообщение в чате, файл) и составьте карту: где проверка есть, где её нет, где она формальная. Такая карта честнее любого отчёта «мы всё изолировали», потому что показывает конкретные двери, а не общее впечатление.

Проверка на практике: контекст компании в запросе

Начните с одного объекта и одного сквозного теста: создайте в условной компании A документ, попробуйте достать его из компании B по прямому идентификатору, через поиск, по ссылке на файл и через экспорт. Если хотя бы один путь отдаёт данные — у вас есть конкретная задача, а не абстрактное «улучшить безопасность». Дальше расширяйте проверку на очередь, кеш и фоновые задания, пока каждый слой не начнёт требовать явный тенант.

Смежный вопрос разобран отдельно: Несколько брендов в одной CRM: от чьего имени отвечает агент.

Источники и границы разбора

Источники и статус проверки на 9 ноября 2026. Первичные документы поясняют указанные механизмы; доступность и границы каждого источника отмечены ниже. Методические рекомендации и условные примеры составляют самостоятельную часть разбора.

  1. Разработка под задачу

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

  2. Microsoft: Multitenant architecture

    Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.

  3. Microsoft: Security trimming in search

    Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.

  4. OWASP: Authentication Cheat Sheet

    Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.

Частые вопросы

Чем аутентификация отличается от авторизации тенанта?

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

Почему тенант нельзя хранить в глобальной переменной?

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

Что делать, если в сообщении очереди забыли тенант?

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

Как проверить, что поиск не отдаёт чужие документы?

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

Как поступать с пользователем, который работает в двух компаниях?

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

404ai · задача вашей компании

Обсудим вашу задачу

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

Укажите телефон или Telegram для ответа

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

Заявка принята. Свяжемся, уточним задачу и согласуем следующий шаг.

Можно также написать напрямую: Telegram 404ai.

Подробнее: Разработка под задачу
Разбор задачи — бесплатноОтвет за 15 минут
Разобрать задачу