AI-агенты

Права доступа в RAG: фильтрация документов и проверка отзыва прав

Практическое руководство по серверной фильтрации документов в RAG: какие данные подготовить, как перенести ACL на каждый chunk, исключить закрытый текст из контекста и кэша и проверить отзыв прав.

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

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

Ниже — схема требований и учебная проверка на синтетических данных. Это не отчёт о клиентском внедрении и не результат живого испытания конкретного продукта. Названия S, F, F_user, FIN-01, номера версий и содержимое запросов условны.

Какую задачу решает этот процесс

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

Для этого недостаточно скрыть ссылку в интерфейсе или добавить в промпт фразу «не раскрывай секреты». Закрытый фрагмент не должен появиться ни на одном из следующих этапов:

  1. в результатах поиска;
  2. в контексте, который получает модель;
  3. в цитатах и ссылках ответа;
  4. в общем или ранее заполненном кэше.

Граница руководства — авторизация каждого найденного документа и тесты изоляции между ролями. Здесь не рассматриваются маскирование отдельных полей внутри разрешённого документа и роли администраторов n8n. Управление доступом к самому n8n — соседняя задача; её можно разобрать в руководстве о ролях и доступе команды.

Практический критерий прост: если сотруднику запрещён FIN-01, ни точная цитата из него, ни прямая ссылка, ни повтор старого запроса не должны вернуть его текст. Если система не может установить актуальные права, безопасный результат — deny, а не попытка угадать доступ.

Какие данные и правила подготовить

Права RAG-поиска зависят не от одного поля. Нужно связать пользователя, его эффективные группы, ACL исходного документа, метаданные фрагмента, состояние индекса и кэш. У каждого значения должен быть источник и ответственный.

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

ПолеУсловное значениеИсточникОтветственныйСтатус
user_idSКаталог пользователей или IdPВладелец управления доступомОбязательное
groups(S)∅IdP или сервис группВладелец управления доступомОбязательное
document_idFIN-01Система-источник документаВладелец корпуса документовОбязательное
ACL(FIN-01){F}Права в системе-источникеВладелец корпуса и владелец доступаОбязательное
chunk_idFIN-01-C1Конвейер разбиения документовРазработчик интеграцииОбязательное
index_acl{F}Метаданные записи в индексеРазработчик интеграцииОбязательное
permission_version7Сервис прав или журнал их измененийВладелец доступаОбязательное
Состояние отзывачленство действуетIdP и журнал измененийВладелец доступаОбязательное
cache_keyхеш пользователя, запроса, корпуса и версии правКонфигурация приложения и кэшаРазработчик интеграцииУсловное: если ответы кэшируются
TTLопределяется проектомКонфигурация кэшаВладелец эксплуатацииУсловное: не заменяет отзыв
Задержка синхронизациинеизвестнаЗамер выбранной интеграцииРазработчик и владелец доступаНеизвестное до проверки
Способ получения группнеизвестенAPI или токен выбранного IdPВладелец доступаНеизвестное до проверки
Серверная ACL-фильтрациянеизвестнаДокументация и тест выбранного индексаРазработчик интеграцииНеизвестное до проверки

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

Обязательное поле нельзя молча заменить пустым значением. Например, отсутствие index_acl не означает, что фрагмент публичный. Безопасное правило: не индексировать такой chunk или исключать его из выдачи до разбора причины.

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

Где должны применяться права доступа

Аутентификация отвечает на вопрос «кто пользователь?». Авторизация отвечает на другой вопрос: «какие документы этому пользователю разрешено получить сейчас?». Успешный вход не даёт права читать весь индекс.

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

allowed(u, d) = true, если effective_groups(u) пересекается с allow_groups(d) и ни одно применимое правило deny не сработало.

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

Безопасная последовательность выглядит так:

  1. Сервер устанавливает user_id из доверенного механизма аутентификации.
  2. Получает актуальные группы и версию разрешений из согласованного источника.
  3. Добавляет ACL-фильтр к поисковому запросу, чтобы retrieval возвращал только разрешённые документы и chunks.
  4. Передаёт в контекст модели только результат этого отфильтрованного retrieval.
  5. Строит цитаты и кэш из того же разрешённого набора.

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

Официальная документация Azure AI Search описывает document-level access control и security trimming: идентификатор пользователя или группы передают как фильтр, а несовпадающие документы исключаются из результатов. Для встроенных ACL-, RBAC-, SharePoint- и Purview-вариантов в документации указаны отдельные условия, причём часть возможностей имеет статус preview. Это пример документированного подхода конкретной платформы, а не доказательство совместимости с выбранной системой заказчика.

Как перенести ACL в поисковый индекс

Индекс должен хранить связь фрагмента с исходным документом и его правами. Если документ FIN-01 разрешён только группе F, каждый созданный из него chunk наследует это ограничение:

ACL(FIN-01-C1) = ACL(FIN-01) = {F}.

Для сотрудника S в учебном примере:

  • groups(S) = ∅;
  • ACL(FIN-01-C1) = {F};
  • ∅ ∩ {F} = ∅;
  • следовательно, allowed(S, FIN-01-C1) = false.

При подготовке записи индекса полезно хранить как минимум document_id, chunk_id, разрешённые субъекты или группы, применимые запреты, источник ACL и версию разрешений. Конкретная схема зависит от поискового движка и модели прав.

Разбиение документа создаёт отдельный риск. Текст может успешно перейти в chunk, а ACL — потеряться. Если пустая ACL трактуется как public, закрытый фрагмент попадёт в retrieval. Это не ошибка модели: дефект возник в конвейере индексации.

Поэтому проверка переноса должна отвечать на четыре вопроса:

  1. Каждый ли chunk связан с исходным document_id?
  2. Перенесён ли эффективный ACL с учётом наследования и deny?
  3. Совпадает ли версия прав в индексе с ожидаемой?
  4. Что делает система, если ACL отсутствует или не разбирается?

Безопасный ответ на четвёртый вопрос — исключить фрагмент из поиска. После исправления метаданных его можно переиндексировать. Автоматически превращать неизвестное право в public нельзя.

В Azure AI Search документация отдельно отмечает перенос полей разрешений в index projections, если skillset разбивает документы на фрагменты. При выборе этой платформы нужно сверить источник данных, схему индекса, API или SDK и статус функции. Для другого индекса тот же принцип следует реализовать его средствами и проверить отдельно.

Как отозвать доступ и проверить кэш

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

В учебной схеме кэш привязан к субъекту и версии прав:

cache_key = hash(user_id, normalized_query, corpus_version, permission_version).

До отзыва условный пользователь F_user входит в группу F, а permission_version = 7. Его разрешённый ответ можно сохранить только в области этого пользователя или эквивалентной области доступа с версией 7.

После удаления F_user из группы F система получает groups(F_user) = ∅ и permission_version = 8. Запись версии 7 больше нельзя читать. Запрос по новому ключу версии 8 даёт miss и снова проходит серверный ACL-фильтр. Закрытый chunk не попадает ни в контекст, ни в новую запись кэша.

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

Нельзя обещать нулевую задержку синхронизации. Для конкретной интеграции нужно определить:

  • откуда приложение получает группы;
  • когда изменяется permission_version;
  • как обновляются права в индексе;
  • какие кэши существуют между запросом и ответом;
  • за какое время отзыв должен стать действующим;
  • как система ведёт себя, пока актуальность прав не подтверждена.

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

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

Учебный пример: путь от входных данных до результата

Рассмотрим синтетический пример, а не клиентский кейс и не результат живого теста. Финансовый документ FIN-01 доступен группе F. Сотрудник S в неё не входит. Отдельный условный пользователь F_user сначала входит в F, а затем владелец доступа удаляет его из группы.

Исходные данные:

  • ACL(FIN-01) = {F};
  • документ разбит на условный фрагмент FIN-01-C1;
  • ACL(FIN-01-C1) = {F};
  • groups(S) = ∅;
  • до отзыва groups(F_user) = {F} и permission_version = 7;
  • после отзыва groups(F_user) = ∅ и permission_version = 8.

Промежуточное решение принимает сервер. Для S пересечение групп с ACL пусто, поэтому chunk исключается до контекста. Для F_user до отзыва пересечение содержит F, поэтому chunk разрешён. После удаления пользователя из группы пересечение снова пусто, а запись кэша версии 7 недоступна запросу версии 8.

РольДокументRetrievalКонтекст моделиКэшОжидаемый ответ
S, не состоит в FFIN-01Запрещён: ∅ ∩ {F} = ∅FIN-01-C1 отсутствуетMiss; закрытый текст не сохраняетсяСообщить, что доступных материалов по запросу не найдено; не возвращать текст и цитату
F_user до отзыва, версия 7FIN-01Разрешён: {F} ∩ {F} = {F}Разрешённый FIN-01-C1 может попасть в контекстЗапись только в ключе с F_user и версией 7Ответ по разрешённому фрагменту с допустимой цитатой
Тот же F_user после отзыва, версия 8FIN-01Запрещён: ∅ ∩ {F} = ∅FIN-01-C1 отсутствуетВерсия 7 недоступна; ключ версии 8 даёт missЗакрытый текст и прежняя цитата не возвращаются

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

Теперь проверим запрос по точной условной цитате из FIN-01. Для S совпадение текста не отменяет ACL: retrieval не возвращает FIN-01-C1. Для F_user до отзыва фрагмент может быть найден и использован. После отзыва тот же запрос не получает старый ответ из кэша и не находит закрытый chunk заново.

Пример показывает важное различие: F — группа, а не пользователь. Отзыв означает удаление F_user из группы F, изменение версии разрешений и недоступность прежней области кэша. Исходный запрет для S и последующий отзыв для ранее допущенного пользователя — две разные проверки.

Учебная схема проверки документа FIN-01 для сотрудника S, пользователя F_user до отзыва и того же пользователя после удаления из группы F

Как проверить результат перед запуском

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

Следующие сценарии — ожидаемая программа приёмки будущей реализации, а не заявление о выполненном тесте.

ПроверкаОжидаемый результатСигнал ошибкиДействие человека
S ищет точную цитату из FIN-01Документ, chunk и цитата отсутствуютFIN-01 появляется в retrieval, контексте, цитате или ответеОстановить запуск, включить безопасный deny, проверить группы и серверный фильтр
S открывает прямую ссылку на FIN-01Сервер повторно проверяет ACL и не отдаёт содержимоеСсылка обходит фильтр поиска и открывает текстЗакрыть маршрут, проверить авторизацию объекта и журналировать попытку
S повторяет запрос, ранее выполненный разрешённой рольюОбщий кэш не возвращает закрытый текстПоявляется ответ, выдержка или цитата другого пользователяОтключить проблемную область кэша, проверить ключ, разделение и инвалидацию
F_user до отзыва обращается к FIN-01Доступен только разрешённый FIN-01-C1Разрешённый фрагмент пропал или добавились документы вне ACLПроверить источник групп, ACL документа и метаданные chunks
F_user удалён из F, версия стала 8Старый ответ версии 7 недоступен; новый поиск не возвращает документЛюбая часть прежнего текста появляется после отзываЗакрыть выдачу, проверить обновление групп, версии прав, индекс и кэш
Актуальные группы или ACL недоступныОтвет не строится из спорного документа; событие передаётся на разборСистема считает неизвестное состояние разрешениемПеревести ветку в deny и восстановить доверенный источник прав
В корпусе есть заявка на предоставление доступаТекст заявки доступен только по её собственным правиламМодель пересказывает заявителя, причину или маршрут согласования без отдельного разрешенияИсключить заявку из контекста и проверить её ACL независимо от FIN-01

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

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

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

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

Что согласовать для внедрения

Готовый поисковый инструмент может предоставить индекс, фильтры и документированный механизм document-level access. Но он не знает автоматически, какой каталог пользователей считать доверенным, как наследуются права конкретного корпуса, когда считать отзыв действующим и как устроен кэш приложения. Эти связи относятся к собственной интеграции.

Перед выбором решения согласуйте:

  • один корпус документов и его владельца;
  • источник пользователей, групп, ACL и событий отзыва;
  • правила allow, deny и наследования;
  • перенос прав на каждый chunk;
  • серверный фильтр для всех видов поиска;
  • область кэша и механизм инвалидации;
  • допустимую задержку синхронизации;
  • набор обычных и отрицательных проверок;
  • ответственного за спорные права и приёмку.

Совместимость, API, тариф, необходимые роли и статус функций проверяются отдельно для выбранной платформы. Статья не подтверждает готовую интеграцию Switch On AI с Azure AI Search или другим вендором и не доказывает выполненное клиентское внедрение. Для Azure AI Search особенно важно отделять общий security trimming на строковых фильтрах от возможностей, которые официальная документация помечает как preview.

Switch On AI разрабатывает ИИ-помощников и интеграции под конкретную операцию после проверки данных и доступных API. Для этого сценария задача формулируется так: пользователь видит только разрешённые фрагменты, включая после изменения доступа. Если системе достаточно фиксированного поиска и фильтра, агентный выбор действий может не понадобиться; если помощник выбирает источник или следующий разрешённый инструмент, его полномочия задают отдельно.

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

Следующий шаг — разобрать модель доступа одного корпуса документов: взять обезличенные примеры разрешённого документа, запрета и отзыва, описать источники прав и согласовать прототип ключевой операции. Прототип не равен полноценному внедрению и не подтверждает работу во всех условиях. Чтобы обсудить исходные данные и критерии проверки, расскажите о задаче Switch On AI.

Вопросы по этой задаче

Можно ли ограничить доступ одним промптом?

Нет. Промпт не заменяет серверную авторизацию. Запрещённый документ нужно исключить из retrieval до передачи фрагмента в контекст модели, а то же решение применить к цитатам, прямым ссылкам и кэшу.

Как учитывать права при разбиении документов?

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

Что делать с уже закэшированным ответом?

Связать кэш с пользователем или эквивалентной областью доступа и permission_version. После отзыва нужно инвалидировать старую запись либо перестать читать её благодаря новой версии прав; затем запрос заново проходит серверный ACL-фильтр.