Как дать нейросети доступ к 1С, чтобы она видела только разрешённое пользователю
Иван Недомолков · Dərc edilib:
Нейросеть, подключённая к 1С, видит ровно то, что видит учётка её коннектора. Если коннектор ходит в базу под служебным пользователем с полными правами, любой сотрудник через чат получит ответ по любым данным: зарплаты, закрытые договоры, чужие клиенты. Роли, профили групп доступа и ограничения на уровне записей (RLS) платформа при этом честно применяет, только к служебной учётке. Чтобы модель видела данные глазами конкретного человека, запрос с данными нужно выполнить в сеансе этого человека. Ниже методика: как проверить свою схему, что не работает, как устроен рабочий вариант и сколько он стоит.
Руководителю из этого разбора хватит первого раздела и таблицы требований к шлюзу. Остальное для программиста, который собирает или настраивает коннектор.
Проверка за пять минут: видит ли ваша нейросеть лишнее
Три вызова через тот же инструмент, которым модель ходит в базу (MCP-сервер, чат-бот, свой HTTP-сервис). MCP здесь и дальше - протокол, по которому нейросеть вызывает внешние инструменты, в том числе запросы к базе.
| Что выполнить | Что значит ответ |
|---|---|
ИмяПользователя() | Имя служебной учётки вместо имени того, кто пишет в чат: база не знает спросившего |
ПривилегированныйРежим() | Истина - права не проверяются вообще. Ложь - проверяются, но у того пользователя, что в первой строке |
| Вопрос от имени рядового сотрудника про данные, которые ему закрыты | Пришёл ответ - права пользователей для модели не действуют |
У коннекторов, которые мы видели, картина одна: адрес публикации базы и одна учётка в настройке клиента, чаще с полными правами, иначе модель не осилит половину вопросов. Бухгалтер, кладовщик и директор приходят к базе одним пользователем.
Строка в системной инструкции вида “не показывай зарплаты тем, кому не положено” ничего не ограничивает. Это просьба к модели, которая продолжает текст, и платформа про неё не знает.
Почему роли и RLS не срабатывают
Права в 1С принадлежат сеансу. Сеанс открывается под логином, и сменить пользователя у открытого сеанса нельзя. Цепочка у типового коннектора такая:
| Звено | Кто там на самом деле |
|---|---|
| Чат | конкретный сотрудник |
| Коннектор (MCP-сервер, бот) | знает сотрудника, если его так написали, но в базу идёт под своей учёткой |
| HTTP-сервис базы | сеанс служебной учётки |
| Роли и ограничения доступа | применены к служебной учётке |
Разрыв в третьей строке. Всё, что настроено для сотрудника, остаётся в его собственных сеансах, а их модель не открывает.
Пять приёмов, которые прав не возвращают
Каждый из них мы сначала считали рабочим и проверили прогоном на демо-базе Управления торговлей 11.5 (стенд описан ниже). Запрос во всех случаях один и тот же, служебная учётка возвращает по нему шесть строк.
| Приём | Ожидание | Что показал прогон | Почему так |
|---|---|---|---|
УстановитьПривилегированныйРежим(Ложь) | права включатся | одиночный вызов падает: “В данной процедуре/ функции не был установлен привилегированный режим”. Пара Истина, затем Ложь проходит, строк по-прежнему 6, пользователь служебный | привилегированный режим в сеансе коннектора и так выключен, проблема в пользователе сеанса |
| Дописать РАЗРЕШЕННЫЕ | недоступные строки отбросятся | 6 строк | у служебной учётки недоступных строк нет |
Спросить ПравоДоступа за сотрудника и фильтровать | функция скажет, что ему можно | Истина на чтение справочника, Истина на просмотр реквизита “Сумма” | функция отвечает про право на объект, про строки под RLS она ничего не знает |
| Фоновое задание | отдельный сеанс под другим пользователем | задание отработало под служебной учёткой | у ФоновыеЗадания.Выполнить нет параметра “от имени” |
| Прописать в коннектор учётку сотрудника | коннектор станет сотрудником | 403 “Недостаточно прав для использования ресурса с данным HTTP методом” на сервисах готового MCP | у методов HTTP-сервиса своё право “Использование”, а служебные справочники коннектора тоже закрыты |
Про ПравоДоступа подробнее: с третьим параметром она принимает роль или пользователя, и для аудита ролей это хороший инструмент, мы им выгружаем права доступа для разбора нейросетью. Для реквизита право называется “Просмотр”: с правом “Чтение” функция падает с ошибкой “Право Чтение неприменимо к … Реквизит.Сумма”. Но конфиденциальность в задаче с зарплатами живёт на уровне строк, и тут функция бесполезна.
Последний приём выглядит почти как решение. Разница в объёме: чтобы весь коннектор работал под сотрудником, каждому придётся раздать права на механику коннектора, его справочники методов и настроек. Права на инструмент смешиваются с правами на данные. Рабочая схема ниже тоже ходит под логином сотрудника, но в один метод.
Рабочая схема: два канала вместо одного
У всех пяти приёмов общий корень: в 1С нет вызова “выполни этот запрос от имени пользователя X” внутри своего сеанса. Значит, нужен сеанс сотрудника, и самый короткий путь к нему, который у нас заработал, - 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(Тело);
КонецФункции
Чтобы увидеть, что именно модель выдала бы сверх положенного, в расширении есть сравнение двух каналов. Оно выполняет запрос обоими способами и возвращает строки, которые спросивший получил бы только через служебную учётку:
Функция Сравнить(ТекстЗапроса, АдресПубликации, Логин, Пароль, Разрешенные = Истина) Экспорт
Служебная = ВыполнитьПодСлужебнойУчеткой(ТекстЗапроса);
Спросивший = ВыполнитьОтИмениСпросившего(ТекстЗапроса, АдресПубликации, Логин, Пароль, Разрешенные);
Видит = Новый Соответствие;
Для Каждого Стр Из Спросивший.строки Цикл
Видит.Вставить(Стр, Истина);
КонецЦикла;
Лишние = Новый Массив;
Для Каждого Стр Из Служебная.строки Цикл
Если Видит.Получить(Стр) = Неопределено Тогда
Лишние.Добавить(Стр);
КонецЕсли;
КонецЦикла;
Возврат Новый Структура("служебная, спросивший, лишние", Служебная, Спросивший, Лишние);
КонецФункции
Стенд и замер
Демо-база Управления торговлей 11.5.22, платформа 8.3.27, клиент-серверный вариант. Расширение без заимствованных объектов, в нём справочник “Начисления” на шесть строк, у двух флажок “Конфиденциально”: директор 250 000 и главный бухгалтер 180 000. Роль бухгалтера даёт чтение справочника с условием в ограничении доступа и право “Использование” на метод POST сервиса:
ГДЕ НЕ Конфиденциально
Тестовый пользователь заводится с этой единственной ролью. Запрос, будто бы написанный моделью на вопрос “сколько у нас получают сотрудники”:
ВЫБРАТЬ
Д.Должность,
Д.Сумма
ИЗ
Справочник.пс_ДемоНачисления КАК Д
УПОРЯДОЧИТЬ ПО
Д.Сумма УБЫВ
| Канал | Пользователь сеанса | Строк | Сумма | Время |
|---|---|---|---|---|
| служебная учётка, запрос как есть | служебный | 6 | 830 000 | 1-5 мс (серия 2, 5, 1, 1, 1) |
| сеанс спросившего, запрос как есть | бухгалтер | ошибка прав | - | 32 мс |
| сеанс спросившего, с РАЗРЕШЕННЫЕ | бухгалтер | 4 | 400 000 | 33-64 мс (серия 64, 48, 34, 33, 34) |
Прочерк значит “не мерили”. Лишние строки служебного канала: “Директор | 250000” и “Главный бухгалтер | 180000”. Вызовы шли по http внутри локальной сети. Через прокси и https будет дольше, насколько - не проверяли. На фоне времени, за которое модель пишет ответ, десятки миллисекунд не заметны.
Ошибка или неполный ответ: выбор, который делаете вы
Запрос без РАЗРЕШЕННЫЕ в сеансе бухгалтера упал с ошибкой “У пользователя недостаточно прав на исполнение операции над базой данных”. Так платформа ведёт себя всегда, когда запрос натыкается на строку, закрытую RLS. С РАЗРЕШЕННЫЕ закрытые строки молча отбрасываются.
| Режим | Что получит модель | Чем плохо |
|---|---|---|
| Без РАЗРЕШЕННЫЕ | ошибку, и честно скажет, что прав не хватило | на любой вопрос, который задевает хоть одну закрытую строку, ответа нет вовсе |
| С РАЗРЕШЕННЫЕ | 4 строки на 400 000 | модель напишет “фонд оплаты труда 400 000” при 830 000 в базе, и неполноту никто не заметит |
Мы оставили РАЗРЕШЕННЫЕ по умолчанию и добавили бы в ответ модели оговорку “по доступным вам данным”. Это наше решение, и у вас оно может быть другим. Ставить слово надо на стороне базы: надеяться, что модель допишет его сама, мы бы не стали.
Функция ДобавитьРазрешенные(ТекстЗапроса) Экспорт
Верх = ВРег(ТекстЗапроса);
Если СтрНайти(Верх, "РАЗРЕШЕННЫЕ") > 0 Или СтрНайти(Верх, "ALLOWED") > 0 Тогда
Возврат ТекстЗапроса;
КонецЕсли;
Для Каждого Слово Из СтрРазделить("ВЫБРАТЬ,SELECT", ",") Цикл
Позиция = СтрНайти(Верх, Слово);
Если Позиция > 0 Тогда
Конец = Позиция + СтрДлина(Слово);
Возврат Лев(ТекстЗапроса, Конец - 1) + " РАЗРЕШЕННЫЕ" + Сред(ТекстЗапроса, Конец);
КонецЕсли;
КонецЦикла;
Возврат ТекстЗапроса;
КонецФункции
Функция демонстрационная. В пакетном запросе она поставит слово только в первый запрос пакета, а слово РАЗРЕШЕННЫЕ в любом месте текста, даже в комментарии, её отключит. Для боевого коннектора текст стоит разбирать через СхемаЗапроса.
Встраивание в свой коннектор по шагам
- Разделить методы коннектора на служебные и методы с данными. Метаданные, справка, список методов остаются под учёткой коннектора. Всё, куда модель передаёт свой текст запроса или кода, уходит в сеанс спросившего.
- Научить коннектор знать, кто спросил. В чат-боте это пользователь чата, в клиенте нейросети на машине сотрудника - его личная настройка подключения.
- Завести маленькую роль с одним правом “Использование” на метод POST сервиса запроса и добавить её в профили групп доступа. К другим HTTP-сервисам базы она доступа не даёт.
- Проверить публикацию. Веб-сервер должен публиковать HTTP-сервисы расширений, параметр
publishExtensionsByDefault="true"в default.vrd, иначе сервис не найдётся. Если коннектор переиспользует сеансы, новая версия расширения видна только в новом сеансе: на стенде первые три вызова после обновления вернули “Метод объекта не обнаружен”. - Убрать привилегированное с пути запроса. Ни привилегированных общих модулей, ни
УстановитьПривилегированныйРежим(Истина). Признак режима отдавать в ответе. - Дописывать РАЗРЕШЕННЫЕ в базе, если выбрали этот режим.
- Убрать старый инструмент выполнения запросов под служебной учёткой. Если он остался, модель пойдёт туда, и новый метод ничего не изменит.
Требования к шлюзу: пароль спросившего
Чтобы открыть сеанс бухгалтера, коннектор передаёт его логин и пароль на каждом вызове. Кто держит коннектор, тот держит учётные данные всех, кто спрашивает. Шлюз здесь - программа между чатом и базой: бот или MCP-сервер.
| Требование | Зачем |
|---|---|
| Пароль подставляет шлюз, модель его не видит | параметры инструмента попадают в контекст модели, историю чата и логи облачного провайдера, а модель сможет вызвать сервис под чужим логином |
| Только https или вызов внутри одного сервера | по http логин и пароль идут в заголовке Basic, это base64, почти открытый текст |
| Отдельная учётка на чтение вместо личного пароля сотрудника | те же права на данные и роль сервиса, без интерактивного входа. Утечка отдаст только чтение |
| Помнить, от чего схема не защищает | учётка коннектора с полными правами может завести пользователя или сменить пароль, демо-прогон расширения так и делает |
| Проверить лицензии и лимиты сеансов | каждый вызов открывает на сервере новый сеанс под пользователем, мы это не мерили |
Токен вместо пароля и вход через ОС в прогоне не проверялись. Для схемы нужно одно: чтобы веб-сервер открыл сеанс под нужным пользователем, способ входа может быть любым, который поддерживает ваша публикация.
Если модель исполняет код, а не только запросы
Та же схема, только осторожнее. Код в сеансе бухгалтера может записать то, на что у бухгалтера есть право. Серверный код вне безопасного режима может сам вызвать УстановитьПривилегированныйРежим(Истина) и снять RLS, поэтому код модели выполняется только после УстановитьБезопасныйРежим(Истина). На стенде вариант с кодом мы не гоняли, это рассуждение по устройству платформы.
От записи защищает другой приём, транзакция с безусловным откатом для кода агента. Чтение чувствительных данных она не закрывает, и сеанс спросившего закрывает как раз эту часть. Вместе они дают агенту читать только разрешённое и ничего не оставлять в базе.
Что осталось за границей проверки
- Группы доступа БСП. В базе прогона RLS БСП выключен константой, демо шло на своём справочнике с условием в роли расширения. Условия БСП тоже живут в ролях и применяются платформой к сеансу, так что ожидаем того же поведения, но прогона нет.
- Готовые методы коннектора. Если метод сам собирает данные (остатки, продажи) под служебной учёткой и отдаёт модели результат, RLS к нему не применится. Каждый такой метод переводится в сеанс спросившего отдельно.
- Проверка сертификата у
ЗащищенноеСоединениеOpenSSLбез указания удостоверяющих центров на 8.3.27 не проверялась. - Лимиты на запрос. Сервис выполняет любой запрос без ограничения по времени и числу строк. К правам это не относится, но в боевом коннекторе лимит нужен.
Остаётся вопрос, на который права не отвечают вовсе. Роли решают, что модель прочитает, но не решают, куда прочитанное уйдёт. Если нейросеть облачная, строки, которые бухгалтеру разрешено видеть, всё равно уезжают на чужой сервер, и тут работают выбор модели и обезличивание данных перед отправкой в нейросеть.
Готовое расширение
Всё из разбора собрано в расширение “Права спросившего”: HTTP-сервис запроса, общий модуль с обоими каналами, сравнением и проверками пяти отброшенных приёмов, две демо-роли и демо-справочник. Прогон целиком запускается одной строкой из консоли кода или через MCP:
пс_ПраваСпросившего.ВJSON(пс_ПраваСпросившего.ДемоПрогон("http://сервер/база"))
Функция заводит тестового пользователя с одноразовым паролем, выполняет оба канала и проверки и удаляет пользователя за собой. Расширение ставится без заимствований и базу не меняет, кроме своего справочника. Описание и демо - на странице Права спросившего для нейросети, файл - в карточке на Инфостарте.