Секрет в аргументе вызова MCP: что видит агент, что хранит журнал, что получает 1С
Администрирование - Информационная безопасность
Файлы
ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.
Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний
Оформите подписку на компанию для решения рабочих задач
Оформить подписку и скачать решение со скидкойВы можете заказать платную доработку или адаптацию этой разработки под вашу конфигурацию на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.
При подключении ИИ-агента к 1С через MCP-сервер нужно проверить, куда могут попасть секреты: например, токен доступа в параметрах запроса или в тексте ответа. В нашем прототипе такой сервер запрашивает записи журнала регистрации у сервиса 1С и возвращает их MCP-клиенту. Я проверила, может ли секрет попасть в возвращаемые данные, сохраниться в журнале самого сервера или уйти в запросе к 1С.
Проверка показала: успешный вызов инструмента ещё не означает, что утечки нет. В одном сценарии тестовая строка отсутствовала в ответе, но сохранилась в журнале сервера и ушла в адресе запроса к 1С. В другом секрет вернулся клиенту внутри разрешённого поля комментария. Поэтому проверять нужно каждый из этих путей, включая вызовы, завершившиеся ошибкой.
Я разбираю шесть сценариев до и после изменения кода: где нужна проверка входных аргументов, как очищать журнал и почему допустимое текстовое поле ответа требует отдельной проверки содержимого. После правки в сценариях A-E тестовый секрет отсутствует во всех трёх проверяемых местах. В сценарии F строка в форме UUID без имени поля по-прежнему возвращается клиенту: распознавание по заданным признакам её пропускает.
Опыт проведён на Python-прототипе с mcp 2.2.0. Вместо настоящей 1С работает HTTP-заглушка - небольшой сервис, который принимает запросы и возвращает подготовленные ответы. Все секреты заменены синтетическими контрольными строками; модель ИИ в проверке не участвует. Результаты относятся к этому прототипу и показывают, как проверить аналогичную интеграцию на своём стенде.
Шесть сценариев проверки утечек в MCP-сервере между ИИ-клиентом и 1С. Для каждого случая сопоставлены ответ клиенту, журнал сервера и запрос к 1С. После правки в A-E секрет отсутствует на всех проверяемых выходах; F показывает ограничение распознавания UUID без имени поля.
- Успешный вызов с
isError=Falseне исключает утечки: в сценарии A секрет уже был в журнале и в URL к 1С. - Отказ на разборе аргументов тоже пишет журнал: очистку ставят в саму функцию записи.
- Свободный текст ответа проверяется отдельным валидатором, ответ с секретом отклоняется целиком.
- Строка в форме UUID без имени поля проходит и после правки: это граница распознавания по форме.
Проверка утечки - это три колонки на каждый сценарий: что получил агент, что записал журнал, что ушло в 1С. Один флаг isError этого не показывает.
Для кого: разработчикам MCP-серверов и интеграций, через которые агент читает данные 1С.
🔬 Что именно измеряет проверка
Под дверью я понимаю Python-сервер между MCP-клиентом и сервисом 1С. У него есть инструмент event_log: клиент передаёт имя метаданных, время начала выборки и необязательный список событий. Дверь запрашивает записи, проверяет их контракт и возвращает результат. Каждый вызов она записывает в собственный журнал, включая часть отказов, произошедших до выполнения функции инструмента.
Для проверки утечек настоящая база не нужна. Скрипт check_leaks.py поднимает HTTP-заглушку на локальном адресе, загружает модуль двери и вызывает инструмент через Client. Заглушка хранит полученные URL и отвечает заранее подготовленными записями. Все данные опыта синтетические. Тестовые строки в листинги статьи я не переношу: для понимания результата достаточно обозначения «контрольная строка» и описания её формы.
В прогоне используются такие настройки:
Локальный адрес относится только к заглушке. Скрипт запускает её в отдельном потоке и отключает стандартный вывод HTTP-обработчика. Проверка запроса опирается на список URL, который заполняет сам обработчик. Здесь нет наблюдения за журналом регистрации платформы или за журналом промышленного веб-сервера.
Колонка «агент видит» означает, что контрольная строка обнаружилась в результате MCP-вызова. Скрипт собирает текстовые элементы ответа вместе со структурированным содержимым. Модель в опыте не участвует: речь о данных, доступных MCP-клиенту для дальнейшей передачи агенту. В сценарии A исходную строку уже передал клиент, поэтому «нет» в этой колонке означает только отсутствие строки в возвращённом результате.
«Журнал хранит» проверяется по добавленной части файла. Перед вызовом скрипт читает прежнее содержимое, после вызова берёт хвост и ищет в нём контрольную строку. Старые записи не могут дать положительный результат для следующего сценария. Колонка «1С получила» проверяет присутствие этой же строки в декодированных URL, принятых заглушкой за текущий вызов.
Последнее определение особенно существенно для C-F. Заглушка получает запрос с обычными фильтрами, затем сама возвращает секрет внутри JSON. В колонке «1С получила» будет «нет», поскольку входящий URL не содержит контрольную строку. Это значение ничего не говорит об отсутствии обращения к сервису. В D-F источник секрета находится на стороне ответа 1С.
🧪 Что изменила правка
Сначала я сверила состав опыта с его выводом. В A строка попадает в разрешённый строковый аргумент metadata вместе с меткой token=. В B она занимает since_utc, где ожидается время с часовым поясом. Выражение «вне схемы» здесь означает неподходящее значение существующего аргумента; дополнительного аргумента с отдельным именем тест не добавляет.
В C ответ содержит лишнее поле form_snapshot. Внутри него находится контрольная строка. В D секрет записан в разрешённый комментарий после имени поля и знака равенства. В E тот же комментарий содержит строку в форме JWT без имени поля. В F комментарий содержит строку в форме UUID, также без контекста, который назвал бы её секретом.
До правки получена такая таблица. «Видит» и «хранит» относятся именно к контрольной строке, а колонка 1С относится к URL запроса:
| Сценарий | Агент видит | Журнал двери хранит | 1С получила | isError |
|---|---|---|---|---|
A: секрет в metadata |
нет | да | да | False |
| B: секрет вместо времени | нет | да | нет | True |
| C: лишний снимок формы в ответе | нет | нет | нет | True |
| D: секрет в комментарии с именем поля | да | нет | нет | False |
| E: строка формы JWT в комментарии | да | нет | нет | False |
| F: строка формы UUID в комментарии | да | нет | нет | False |
После правки результат изменился так:
| Сценарий | Агент видит | Журнал двери хранит | 1С получила | isError |
|---|---|---|---|---|
A: секрет в metadata |
нет | нет | нет | True |
| B: секрет вместо времени | нет | нет | нет | True |
| C: лишний снимок формы в ответе | нет | нет | нет | True |
| D: секрет в комментарии с именем поля | нет | нет | нет | True |
| E: строка формы JWT в комментарии | нет | нет | нет | True |
| F: строка формы UUID в комментарии | да | нет | нет | False |
В A до правки функция принимала строковый аргумент, формировала запрос и получала корректную запись. Контракт ответа выполнялся, поэтому вызов завершался успешно. Секрет при этом уже оказывался в двух местах: в аргументах журнальной записи и в запросе к следующему сервису. Отсутствие его в результате не отменяло этих записей.
В B тип аргумента остановил вызов до запроса. Однако аудит отказа сохранял исходные аргументы. Получился вызов с ошибкой и с утечкой в собственный журнал. Этот сценарий я бы включила в проверку любого аналогичного прототипа: механизм, который помогает разбирать неверный ввод, сам может сохранить содержимое отвергнутого поля.
В C уже работал запрет дополнительных полей ответа. Снимок формы не соответствовал модели записи и вызывал отказ. В D и E структура ответа оставалась допустимой: комментарий был строкой в разрешённом поле. Проверка типов пропускала его содержимое до появления отдельного валидатора секретов. F показывает границу этого валидатора и после правки.
🚪 Как остановлен секрет во входных аргументах
Правка входа состоит из проверки перед HTTP-вызовом и маскирования при аудите. Эти операции решают разные задачи. Первая препятствует отправке распознанной строки следующему сервису. Вторая очищает журнал, в том числе когда отказ произошёл ещё при разборе аргументов SDK.
Внутри event_log аргументы собираются после типизации времени. Затем проверяются metadata и элементы events. Выдержка из реализации:
Положение блока в функции определяет результат A после правки. Проверка выполняется до call_1c, так что обнаруженный секрет не успевает стать частью URL. В журнале остаётся статус refused, а текст ошибки содержит причину и TraceID. Значение, которое вызвало отказ, в сообщение специально не подставляется.
Распознавание основано на SECRET_RE. Оно ищет значения после ряда названий поля, строку, похожую на JWT, и конструкцию Bearer с последующим значением. Наличие такой проверки не означает, что дверь умеет определять любые секреты по смыслу. Список форм ограничен регулярным выражением; в F это видно непосредственно по результату.
Время проверяет SDK через тип AwareDatetime. Если строка не превращается в ожидаемое время, тело event_log может не начаться. Для таких отказов у двери есть наследник MCPServer, который перехватывает ToolError вокруг вызова инструмента. Через него журналируется B. Защита только внутри функции оставила бы этот путь без очистки.
У прототипа также есть отдельное различие между refused и crashed: первое относится к отказу до обращения к 1С, второе к сбою внутри двери. Исключения, которые инструмент уже записал как DoorError, обходятся без повторной записи на этом уровне. В комментарии реализации отмечено поведение mcp 2.2.0: SDK может обернуть исходное исключение, поэтому проверяется и __cause__.
Для вашего инструмента я бы сначала выписала все точки, которые записывают ошибку. Список обычно начинается внутри функции и продолжается обработчиком SDK. Затем для каждой точки нужно определить, доступны ли там исходные значения и через какую процедуру они проходят перед сохранением. Одной проверки успешного пути в такой схеме мало.
📝 Как очищается журнал
Журнал двери хранит идентификатор вызова, инструмент, аргументы, статус, длительность и краткую причину. В успешной записи нет полного тела ответа: в detail попадает количество записей. Поэтому «нет» в журнальной колонке D-F до правки связано с составом журнала. Секрет мог пройти в ответ агенту, хотя аудит этого ответа его не сохранял.
Очистка включена в саму функцию записи. Если разные ветви вызывают log_call, они получают одну и ту же обработку аргументов:
Это фрагмент из прототипа с сокращённой сигнатурой. Сначала очищается структура, затем выполняется сериализация JSON. Такой порядок сохраняет работу с отдельными строковыми значениями: регулярному выражению не приходится разбирать кавычки и экранирование уже готовой JSON-строки.
Рекурсивный обход реализован следующим образом:
Обход очищает значения словаря. Имена его ключей сохраняются как есть. В наборе A-F секреты находились в значениях; эксперимент не проверял вариант с секретом в имени ключа. Этот предел полезно видеть прямо в коде, если вы собираетесь переносить функцию на структуры с произвольными именами полей.
redact заменяет найденное значение на ***, сохраняя распознанное имя поля. В выводе после правки для A остаётся аргумент с token=***, а для B значение since_utc заменено маской. По такой строке можно понять место отказа, не копируя контрольное значение в журнал. Текст деталей очищается до ограничения длины.
В перехватчике SDK есть дополнительный порядок действий: значения аргументов превращаются в строки и ограничиваются длиной до вызова log_call. Проверка A-F подтвердила результат для своих строк. Отдельного опыта с длинным значением, которое обрезает распознаваемую конструкцию посередине, нет. Обещание очистки произвольного длинного ввода из этой таблицы не следует.
Ошибки ответа требуют такой же осторожности. Вместо вывода всего ValidationError функция собирает пути полей и сообщения из e.errors(). Сами входные значения в эту сборку не включаются. Далее журнал очищает детали, а наружный обработчик очищает текст ToolError, который получает клиент. Здесь полезен TraceID: он связывает отказ с аудитом без необходимости возвращать проблемную строку.
Отдельный прогон контракта на mcp 2.2.0 и pydantic 2.13.5 дал 11 строк журнала: два refused, два unavailable, шесть rejected и один ok. Это другой набор проверок, его не нужно суммировать с шестью сценариями утечек. Он подтверждает, что аудит охватывает и отказ схемы, и проблемы доступности, и корректный ответ.
📤 Почему свободный текст ответа проверяется отдельно
Модель записи задаёт допустимые поля. В ней разрешены время UTC, маскированное имя пользователя, событие, метаданные, ссылка и необязательный комментарий. У модели установлен extra="forbid". Поэтому C завершается отказом: form_snapshot вообще не входит в контракт, независимо от содержимого снимка.
Для разрешённых строк применяется отдельный валидатор:
Фрагмент показывает различие между очисткой аудита и проверкой результата. Журнал получает маску. Ответ с распознанным секретом в этих полях отклоняется целиком. Для D и E после правки это означает isError=True: агенту не выдаётся частично исправленная запись с прежним статусом успеха.
Такое поведение помогает заметить ошибку источника данных. Если сервис 1С возвращает комментарий с чувствительным содержимым, маска в одной записи могла бы скрыть сам факт нарушения контракта. Отказ требует разобрать, откуда поле получило значение, и решить, какую информацию вообще разрешено включать в ответ. В этой реализации выбор сделан явно.
Остальные поля проверяются по своим правилам. Время должно иметь часовой пояс и смещение UTC; пользователь должен соответствовать шаблону маски; ссылка проверяется регулярным выражением. Общего вызова no_secret для каждого поля модели нет. Поэтому я рассматриваю результат D-E как проверку перечисленных полей, без обещания поиска секрета в любом месте JSON.
Сценарий F прошёл. Строка в форме UUID без подписи похожа на множество обычных идентификаторов, в том числе на TraceID. В прототипе её может распознать контекст вроде имени поля со знаком равенства; самостоятельная UUID-строка не соответствует выбранным признакам. В записанном опыте после правки агент видит её и получает isError=False.
При проектировании ответа к 1С я бы начала с перечня разрешённых полей и назначения каждого из них. Для комментария нужно отдельно решить, требуется ли агенту исходный текст вообще. Если достаточно события и ссылки на объект, можно уменьшить объём свободного содержимого в контракте. Это проектное предложение; его влияние на текущий набор A-F я не измеряла.
Для повторения опыта удобен такой порядок действий: запустить шесть случаев на прежней двери, сохранить таблицу, затем повторить их на исправленной. В скрипте допускается путь к модулю двери как аргумент, поэтому сравнение использует один набор вызовов. Смена тестовых данных между прогонами осложнила бы ответ на вопрос, какая именно правка изменила результат.
Команды, записанные для этого опыта, выглядят так:
Диапазон зависимости соответствует комментарию проверочного скрипта. Опубликованная таблица относится к mcp 2.2.0. После установки по диапазону вы можете получить другой выпуск; его результат следует сохранить отдельно и подписать установленной версией. Прежний модуль нужно передавать отдельным файлом, не заменяя им исправленную дверь.
При разборе нового вывода я сначала смотрела бы на A и B. Там проверяется исходящий путь и аудит отказов. Затем на C-E, где проверяется ответ источника. F должен остаться отдельной строкой отчёта, даже если остальные значения совпадают с ожидаемыми. Иначе формулировка «после правки секретов нет» потеряет подтверждённое исключение.
🧭 Ограничения
Таблицу «после правки» я повторила 05.10.2026 на текущем коде: Windows 11, Python 3.12.10, mcp 2.2.0, pydantic 2.13.5. Все шесть строк совпали с первым прогоном, включая проход F.
В опыте работает заглушка 1С. Она подтверждает получение контрольной строки в URL, но не подтверждает запись в журналы настоящей платформы, прокси или веб-сервера. Не проверялись журналы MCP-клиента и история модели. Для A клиент уже располагал исходной строкой до вызова; запрет отправки не удаляет её из прежнего контекста.
Распознавание ограничено регулярным выражением и перечисленными точками применения. UUID без контекста прошёл к агенту. Не проверяла секреты в именах ключей, в длинных обрезанных аргументах, в иных кодировках и в других полях ответа. Маска пользователя проверяет форму строки; вывод A-F не доказывает обезличивание любых персональных данных.
isError отражает результат вызова инструмента. В A до правки он равен False при наличии утечки, а в B равен True при сохранении секрета в журнале. Для приёмки двери этот флаг нужно читать вместе с проверкой отдельных выходов. Таблица не даёт оснований объявлять наш прототип защищённым от любого раскрытия данных.
📚 Источники
- MCP Tools, спецификация 2025-06-18: описание схем инструментов и признака ошибки результата. Реализация аудита и таблица A-F относятся к нашей двери.
- MCP Security Best Practices: контекст проверки безопасности MCP-интеграций. Сейчас адрес ведёт на редакцию 2025-11-25.
- Проверка
check_leaks.pyи её сохранённый вывод на mcp 2.2.0: источник шести сценариев, определения колонок и сравнения до/после. Кодarchive_door.py: источник фрагментов очистки, аудита и проверки ответа. Проверка контракта на mcp 2.2.0 и pydantic 2.13.5: источник распределения статусов журнала. - Предшественники на Infostart: MCP-сервер внутри 1C:EDT: даём AI-агентам «руки», не открывая ни одного порта наружу (galich, 08.07.2026); Пароль СУБД из 1CV8Clst.lst достать нельзя? Можно. Проверьте свой кластер 1С (nedomolkov.ivan, 06.08.2026). Здесь разобраны измеренные пути контрольной строки через аргумент, аудит и ответ MCP-прототипа.
📎 Файл к статье
В архиве к статье - 13.3-companion.zip: проверка check_leaks.py, исправленный сервер archive_door.py, таблица ожиданий ozhidaniya-A-F.md и README с версией SDK и смыслом колонок. Скрипт работает против HTTP-заглушки и принимает путь к проверяемому модулю: так можно сравнить свою правку с исходной реализацией. Сценарий F проходит и на исправленном сервере, это записано в таблице ожиданий.
Вступайте в нашу телеграмм-группу Инфостарт