Бухгалтер спросил нейросеть про зарплату директора, и она ответила. Права на это у бухгалтера нет
Шесть строк вернул запрос нейросети, выполненный под служебной учёткой MCP-коннектора (MCP - протокол, по которому нейросеть вызывает внешние инструменты, в том числе запросы к базе), и четыре строки тот же запрос в сеансе бухгалтера. Две строки разницы - начисления директора и главного бухгалтера. В первом случае модель ответила бы бухгалтеру про обе зарплаты, и ни одна проверка прав в 1С этому не помешала бы: права платформа проверила, только у служебной учётки.
Вопрос этот не мой. Под популярной статьёй про нейросеть, которая отвечает по данным 1С, его задал Strady: "А что будет, если в чате Петя спросит "Какая ЗП у Васи"?" (комментарий 64). В той же ветке ещё трое спрашивали про то же самое с разных сторон, по существу им никто не ответил. Я собрал небольшое расширение, которое выполняет запрос модели двумя способами и показывает разницу строк, и прогнал его на тестовой базе Управления торговлей 11.5. Ниже по порядку: где права теряются, какие очевидные способы их вернуть не работают и что сработало.
Где теряются права
MCP-коннекторы к 1С, которые я видел, устроены одинаково. В настройке клиента лежит адрес публикации базы и одна учётка, чаще всего с полными правами, иначе модель половину вопросов не осилит. Нейросеть пишет запрос, коннектор отправляет его в HTTP-сервис базы, сервис выполняет запрос в своём сеансе и возвращает строки. Сеанс открыт под служебной учёткой, и роли с ограничениями на уровне записей (RLS) платформа применяет к ней.
Кто задал вопрос в чате, база не знает. Бухгалтер, кладовщик и директор приходят к ней одним и тем же пользователем. Всё, что настроено в группах доступа, профилях и ролях, для нейросети не существует: права проверяются у сеанса, а сеанс чужой.
Отсюда тезис, с которым наверняка кто-то поспорит. Пока модель ходит в базу под одной учёткой, права пользователей 1С для неё не работают, и никакой промпт этого не исправит. Строка "не показывай зарплаты тем, кому не положено" в системной инструкции - это просьба к модели, которая умеет только продолжать текст. Ограничением она не является. Проверить права за пользователя изнутри сеанса служебной учётки платформа тоже не даёт. Обойти это я пробовал пятью способами.
Стенд
Расширение без заимствованных объектов, ставится в любую конфигурацию на платформе 8.3.27. В нём демо-справочник "Начисления" на шесть строк, у двух стоит флажок "Конфиденциально": директор 250 000 и главный бухгалтер 180 000. Роль бухгалтера даёт чтение справочника с условием в ограничении доступа:
ГДЕ НЕ Конфиденциально
Пользователь для проверки заводится с этой единственной ролью. Служебная учётка, через которую в базу ходит модель, имеет роль "Полные права". Запрос, который якобы написала нейросеть на вопрос "сколько у нас получают сотрудники", самый обычный:
ВЫБРАТЬ Д.Должность, Д.Сумма ИЗ Справочник.пс_ДемоНачисления КАК Д УПОРЯДОЧИТЬ ПО Д.Сумма УБЫВ
Под служебной учёткой он возвращает шесть строк, сумма 830 000. Задача: получить то, что увидел бы бухгалтер, не меняя учётку коннектора.
Что не работает
Каждый пункт ниже я сначала считал рабочим и проверял прогоном, числа из журнала прогона.
Выключить привилегированный режим
Первая мысль: раз права не применяются, значит где-то включён привилегированный режим, выключим его явно. Одиночный вызов УстановитьПривилегированныйРежим(Ложь) платформа отвергает с ошибкой "В данной процедуре/ функции не был установлен привилегированный режим": выключить можно только то, что включено в этой же процедуре. Если сначала включить, а потом выключить, вызов проходит, запрос возвращает те же шесть строк, а ИмяПользователя() показывает служебную учётку.
Привилегированный режим тут вообще ни при чём, в сеансе коннектора он и так выключен, ПривилегированныйРежим() возвращает Ложь. Права применяются, только это права служебной учётки.
Дописать в запрос РАЗРЕШЕННЫЕ
Слово РАЗРЕШЕННЫЕ велит платформе молча отбросить недоступные строки вместо ошибки. Под служебной учёткой недоступных строк нет, запрос с РАЗРЕШЕННЫЕ вернул те же шесть. Слово нужное, но работает оно только в сеансе того, чьи права надо применить. Ниже оно ещё понадобится.
Спросить ПравоДоступа у пользователя
В статье про аудит прав через нейросеть я разбирал, что ПравоДоступа принимает третьим параметром роль или пользователя и отвечает на вопрос "есть ли право на объект". Кажется, этого хватит: спросим у функции за бухгалтера и отфильтруем ответ модели.
Для пользователя с ролью бухгалтера функция вернула Истина на чтение справочника и Истина на просмотр реквизита "Сумма". Всё правильно: право на объект у него есть. Ограничение на уровне записей решает, какие строки ему видны, и про строки ПравоДоступа ничего не знает. Функция отвечает про таблицу, а конфиденциальность живёт в строках. По дороге поймал ещё мелочь: для реквизита право называется "Просмотр", на "Чтение" функция падает с ошибкой "Право Чтение неприменимо к ... Реквизит.Сумма".
Запустить фоновое задание
Фоновое задание работает в отдельном сеансе, и есть ощущение, что сеанс можно открыть под другим пользователем. Нельзя: задание, запущенное из сеанса коннектора, вернуло имя служебной учётки. Параметра "от имени" у ФоновыеЗадания.Выполнить нет.
Отдать коннектору учётку бухгалтера
Самый прямой ход: пусть каждый сотрудник подключает нейросеть под своим логином. С учёткой бухгалтера я постучался в HTTP-сервисы готового MCP этой базы и получил 403 "Недостаточно прав для использования ресурса с данным HTTP методом". У методов HTTP-сервиса есть своё право "Использование", и у обычного пользователя его нет. Выдать роль коннектора можно, но служебный код MCP читает свои справочники с методами и настройками, и права на них тоже придётся раздавать всем. Смешиваются две вещи: права на механику коннектора и права на данные, о которых спросили. Рабочий способ из следующего раздела тоже ходит под логином бухгалтера, но только в один метод расширения, вся механика коннектора остаётся на служебной учётке.
Общее у всех пяти способов одно: в 1С нет вызова "выполни этот запрос от имени пользователя X" внутри своего сеанса. Роли и RLS платформа применяет к сеансу, сеанс открывается под логином, и сменить пользователя у открытого сеанса нельзя.
Что сработало: сеанс спросившего
Раз права живут в сеансе, нужен сеанс бухгалтера. Самый короткий путь к нему, который у меня заработал, - HTTP-вызов своей же публикации с логином того, кто спросил. Коннектор по-прежнему живёт под служебной учёткой и делает всё служебное: описание метаданных, справку, список методов. А запрос с данными, который написала модель, уходит отдельным вызовом в HTTP-сервис расширения, и платформа открывает для него сеанс под учёткой спросившего.
Сервис на стороне базы ничего не проверяет сам, в этом и смысл:
Функция queryPOST(Запрос) Ответ = Новый HTTPСервисОтвет(200); Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8"); Результат = Новый Структура; Попытка ТекстЗапроса = Запрос.ПолучитьТелоКакСтроку("UTF-8"); Если Запрос.ПараметрыЗапроса.Получить("razreshennye") = "1" Тогда ТекстЗапроса = пс_ПраваСпросившего.ДобавитьРазрешенные(ТекстЗапроса); КонецЕсли; Результат = пс_ПраваСпросившего.ВыполнитьВТекущемСеансе(ТекстЗапроса); Исключение Результат.Вставить("ошибка", КраткоеПредставлениеОшибки(ИнформацияОбОшибке())); КонецПопытки; Результат.Вставить("пользователь", ИмяПользователя()); Результат.Вставить("привилегированный", ПривилегированныйРежим()); Ответ.УстановитьТелоИзСтроки(пс_ПраваСпросившего.ВJSON(Результат), "UTF-8", ИспользованиеByteOrderMark.НеИспользовать); Возврат Ответ; КонецФункции
Общий модуль расширения не привилегированный, и это важно: отметь его привилегированным, и RLS в сеансе бухгалтера выключится так же тихо. В ответ сервис кладёт имя пользователя и признак режима, чтобы по журналу было видно, в чьём сеансе прошёл запрос.
Вызов со стороны коннектора - обычное HTTP-соединение с логином и паролем спросившего:
Функция ВыполнитьОтИмениСпросившего(ТекстЗапроса, АдресПубликации, Логин, Пароль, Разрешенные = Ложь) Экспорт Адрес = РазобратьАдрес(АдресПубликации); Защищенное = ?(Адрес.Схема = "https", Новый ЗащищенноеСоединениеOpenSSL, Неопределено); Соединение = Новый HTTPСоединение(Адрес.Хост, Адрес.Порт, Логин, Пароль, , 60, Защищенное); Путь = Адрес.Путь + "/hs/prava_llm/query" + ?(Разрешенные, "?razreshennye=1", ""); HTTPЗапрос = Новый HTTPЗапрос(Путь); HTTPЗапрос.Заголовки.Вставить("Content-Type", "text/plain; charset=utf-8"); HTTPЗапрос.УстановитьТелоИзСтроки(ТекстЗапроса, "UTF-8", ИспользованиеByteOrderMark.НеИспользовать); Ответ = Соединение.ОтправитьДляОбработки(HTTPЗапрос); Тело = Ответ.ПолучитьТелоКакСтроку("UTF-8"); Если Ответ.КодСостояния <> 200 Тогда Возврат Новый Структура("строк, строки, ошибка", 0, Новый Массив, "HTTP " + Формат(Ответ.КодСостояния, "ЧГ=0") + ": " + Лев(Тело, 300)); КонецЕсли; Возврат ИзJSON(Тело); КонецФункции
Роль бухгалтера кроме чтения справочника даёт ровно одно право: "Использование" на метод POST этого сервиса. К другим HTTP-сервисам базы эта роль доступа не даёт.
Цена схемы: пароль спросившего
Чтобы открыть сеанс бухгалтера, коннектор должен получить его логин и пароль в открытом виде на каждом вызове. Кто держит коннектор, тот держит пароли всех, кто спрашивает. Без этих правил я бы схему не включал:
- Пароль подставляет шлюз, модель его не видит. Шлюз здесь - программа между чатом и базой: бот или MCP-сервер. Если логин и пароль идут параметрами инструмента, они попадают в контекст модели, в историю чата и в логи облачного провайдера, а модель сможет вызвать сервис под чужим логином.
- Только https или вызов внутри одного сервера. По http логин и пароль идут в заголовке Basic, это base64, то есть почти открытый текст. В демо адрес http, потому что это стенд.
- Лучше отдельная учётка, чем личный пароль сотрудника. С теми же правами на чтение и ролью сервиса, без интерактивного входа. Утечёт она - утечёт только чтение. Токен вместо пароля тоже вариант, его я не проверял.
- Защищает схема только от запроса модели. Учётка коннектора с полными правами по-прежнему может завести пользователя или сменить ему пароль, демо-прогон ниже так и делает. И если у коннектора остался свой инструмент выполнения запросов под служебной учёткой, модель пойдёт туда. Его надо убрать, одного нового метода мало.
- Каждый вызов открывает на сервере сеанс под пользователем. Как это ляжет на ваши лицензии и лимиты сеансов, проверьте у себя, я этого не мерил.
Замер
Один и тот же текст запроса, одна база, вызовы через MCP-коннектор, которым я хожу в тестовую базу. Прочерк в таблице значит "не мерил":
| Способ | Пользователь сеанса | Строк | Время |
|---|---|---|---|
| служебная учётка, запрос как есть | служебная | 6 | 1-5 мс |
| служебная, привилегированный режим включён и выключен, РАЗРЕШЕННЫЕ | служебная | 6 | - |
| фоновое задание из сеанса коннектора | служебная | - | - |
| сеанс спросившего, запрос как есть | бухгалтер | ошибка | 32 мс |
| сеанс спросившего, с РАЗРЕШЕННЫЕ | бухгалтер | 4 | 33-64 мс |
Две строки, которые бухгалтер получил бы только через служебную учётку: "Директор | 250000" и "Главный бухгалтер | 180000". Цена проверки прав - лишний HTTP-вызов и новый сеанс, в серии из пяти вызовов от 33 до 64 мс, первый самый медленный. Вызов шёл по http внутри локальной сети, через прокси и https будет дольше. На фоне времени, за которое модель пишет ответ, этого не видно.
Ошибка или тихо обрезанный ответ
Запрос "как есть" в сеансе бухгалтера упал: "У пользователя недостаточно прав на исполнение операции над базой данных". Так платформа ведёт себя всегда, когда запрос без РАЗРЕШЕННЫЕ натыкается на строку, закрытую RLS. Рассчитывать, что модель поставит это слово сама, я бы не стал, поэтому расширение дописывает его после первого ВЫБРАТЬ:
Функция ДобавитьРазрешенные(ТекстЗапроса) Экспорт Верх = ВРег(ТекстЗапроса); Если СтрНайти(Верх, "РАЗРЕШЕННЫЕ") > 0 Или СтрНайти(Верх, "ALLOWED") > 0 Тогда Возврат ТекстЗапроса; КонецЕсли; Для Каждого Слово Из СтрРазделить("ВЫБРАТЬ,SELECT", ",") Цикл Позиция = СтрНайти(Верх, Слово); Если Позиция > 0 Тогда Конец = Позиция + СтрДлина(Слово); Возврат Лев(ТекстЗапроса, Конец - 1) + " РАЗРЕШЕННЫЕ" + Сред(ТекстЗапроса, Конец); КонецЕсли; КонецЦикла; Возврат ТекстЗапроса; КонецФункции
Функция нарочно простая. В пакетном запросе она дописывает слово только в первый запрос пакета, а слово РАЗРЕШЕННЫЕ где угодно в тексте, хоть в комментарии, её выключает. Для демо этого хватает, в боевом коннекторе я бы разбирал текст через СхемаЗапроса.
Дальше у меня нет уверенного ответа. С РАЗРЕШЕННЫЕ бухгалтер получает четыре строки на 400 000, и модель спокойно напишет "фонд оплаты труда 400 000", хотя в базе 830 000. Ответ неполный, и об этом никто не узнает. Без РАЗРЕШЕННЫЕ модель получает ошибку и честно скажет, что прав не хватило, но на любой вопрос, который задевает хоть одну закрытую строку, ответа не будет вовсе. Я оставил РАЗРЕШЕННЫЕ по умолчанию и считаю, что в ответе модели надо писать "по доступным вам данным", но это решение, а не факт.
Как встроить в свой MCP
Схема из прогона переносится почти без изменений.
- Разделить вызовы коннектора на служебные и запросы с данными. Описание метаданных, справку и список методов коннектор получает под своей учёткой. Всё, где модель передаёт свой текст запроса или кода, идёт в сеанс спросившего.
- Коннектор должен знать, кто спросил. В чат-боте это пользователь чата, в клиенте нейросети на машине сотрудника - его собственная настройка подключения. Пароль или токен спросившего приносит шлюз, мимо модели, база его не хранит. Где его хранит шлюз, решаете вы, правила выше в разделе про цену схемы. В демо пароль тестового пользователя каждый раз генерируется заново и живёт одну функцию.
- В сеансе спросившего ничего привилегированного. Ни привилегированных общих модулей на пути запроса, ни
УстановитьПривилегированныйРежим(Истина). Признак режима стоит отдавать в ответе, как в сервисе выше. - Каждому пользователю одно новое право: "Использование" на метод HTTP-сервиса запроса. Отдельная маленькая роль, которую легко добавить в профиль групп доступа. И проверьте, что публикация базы на веб-сервере публикует HTTP-сервисы расширений, иначе сервис просто не найдётся.
- РАЗРЕШЕННЫЕ дописывать на стороне базы, а не надеяться на модель.
С исполнением кода, а не только запросов, всё то же самое, только осторожнее: код в сеансе бухгалтера может и записать то, на что у бухгалтера есть право. А серверный код вне безопасного режима может сам вызвать УстановитьПривилегированныйРежим(Истина) и выключить RLS, поэтому код модели выполняется только после УстановитьБезопасныйРежим(Истина). Для кода я этот вариант на стенде не гонял. Здесь пригодится транзакция с безусловным откатом, о которой я писал раньше. Она не даёт агенту оставить изменения в базе, но чтение чувствительных данных не закрывает, и этот материал как раз про ту часть.
Границы
Демо идёт на своём справочнике с условием в роли расширения. В базе прогона ограничение доступа на уровне записей БСП выключено константой, поэтому на группах доступа БСП я этот механизм не гонял. Условия БСП тоже живут в ролях и применяются платформой к сеансу, так что расчёт на то же поведение есть, но проверки нет, и если у кого-то она будет раньше моей, напишите.
Аутентификацию по токену вместо пароля и вход через ОС в этом прогоне я не проверял. Для схемы важно одно: чтобы веб-сервер открыл сеанс под нужным пользователем, а способ входа может быть любым из тех, что поддерживает ваша публикация.
Всё, что коннектор делает под служебной учёткой, остаётся под служебной учёткой. Если метод коннектора сам собирает данные (остатки, продажи) и отдаёт модели готовый результат, RLS к нему не применится. Каждый такой метод надо переводить в сеанс спросившего отдельно.
И последнее: права решают, что модель прочитает, но не решают, куда уйдёт прочитанное. Если нейросеть облачная, строки, которые бухгалтеру разрешено видеть, всё равно уезжают на чужой сервер. Это отдельный вопрос, и ответ на него лежит в выборе модели и в обезличивании, а не в ролях.
Расширение
Всё из статьи собрано в расширение "Права спросившего: запрос нейросети с правами пользователя". Внутри HTTP-сервис запроса, общий модуль с обоими способами и проверками отброшенных вариантов, две демо-роли и демо-справочник. Прогон целиком запускается одной строкой из консоли кода или из MCP: пс_ПраваСпросившего.ДемоПрогон("http://сервер/база"). Функция заводит тестового пользователя, выполняет оба способа и проверки отброшенных вариантов и снимает пользователя за собой. Расширение ставится без заимствований, базу не меняет, кроме своего справочника.
Другие наши инструменты
- Матрица прав доступа для нейросети (LLM) - выгрузить роли и права в файл и спросить у чата, у кого какие права, до того как пускать модель в базу.
- Выгрузка данных 1С для нейросети - когда модели нужны данные файлом, а не прямой доступ к базе.
- Выгрузка метаданных для LLM - дать модели структуру конфигурации файлом, чтобы она писала запросы по настоящим именам реквизитов.
- Чек-ап СУБД под 1С - проверить сервер СУБД под базой, в которую вы собираетесь пустить нейросеть.
- Анализ кода внешних обработок 1С - проверить, что делает код, который исполняется в базе с правами пользователя.
Вопрос
Под какой учёткой у вас нейросеть ходит в базу, и знает ли ваш коннектор, кто именно задал вопрос? И что вы выбрали бы для бухгалтера: ошибку "недостаточно прав" или ответ по доступным ему строкам без предупреждения?
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.