Читать 7 мин

Почему похожие запросы на подтверждение опасны?

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

Почему похожие запросы на подтверждение опасны?

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

Такую ошибку легко допустить в инструментах для агентов, потому что видимое резюме обычно начинается с наименее важных полей: заголовка, имени хоста, метода и, возможно, короткой команды. Эти поля помогают сориентироваться, но не определяют полномочия. POST на один и тот же адрес может создать безобидный черновик или выпустить изменение в рабочую среду. SSH-команда может вывести версию или удалить директорию. Запрос, отправленный с другим заголовком организации, может пересечь границу доступа, даже если каждое слово в URL осталось прежним.

Я видел, как потоки подтверждений становились бесполезными из-за благих намерений. Кто-то хочет уменьшить число прерываний и объединяет похожие вызовы. Кто-то стремится сделать карточку аккуратнее и сворачивает тело запроса. Кто-то хочет сохранить стабильную метку для аналитики и использует ее для всех действий одного семейства. К моменту, когда проверяющий видит десятую знакомую карточку, система уже приучает его не обращать внимания на детали. Злоумышленнику или ошибающемуся агенту нужен всего один скрытый нюанс.

Одинаковые метки могут скрывать разные полномочия

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

Представьте сервис развертывания с одним адресом POST /v1/releases. Сначала агент отправляет черновик релиза, а позже просит опубликовать его. Если на обеих карточках написано «Запрос на релиз» и указано api.example.test, проверяющему придется открыть боковую панель и прочитать тело, чтобы их различить. На практике повторяющиеся карточки приучают людей к тому, что в панели каждый раз отображается один и тот же шум. После этого приходит запрос на публикацию.

Та же проблема возникает в более обычной работе. API системы контроля исходного кода может использовать один и тот же путь для изменения заголовка запроса на слияние, настройки слияния или списка проверяющих. Облачный API может использовать один путь ресурса для пробного запуска и настоящего изменения, различая их одним полем. Конечная точка тикетной системы может записывать данные в закрытый инцидент или на публичную страницу статуса в зависимости от заголовка. Адрес сообщает, куда пришел запрос. Он не сообщает, что сделает удаленный сервис.

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

  • find build -type f -delete и find build -type f -print
  • git push origin HEAD из личного клона и из каталога сборки релиза
  • curl -X POST с JSON-телом, которое создает черновик, и с телом, которое отправляет сообщение
  • ssh deploy@host с учетной записью только для чтения и с учетной записью, которой разрешено менять файлы сервиса

Важно различать сходство и эквивалентность. Сходство означает, что у двух действий достаточно общего, чтобы визуально объединить их. Эквивалентность означает, что одно подтверждение действительно распространяется на другое действие. Первое относится к представлению. Второе предоставляет полномочия. Если смешать эти понятия, компактный интерфейс незаметно расширит разрешение.

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

Подтверждение должно быть связано с выполненным запросом

Подтверждаемый объект должен содержать каноническое описание действия, которое исполнитель отправит, а также контекст идентичности, в котором он это сделает. Если меняется любое поле, влияющее на полномочия, система должна запросить подтверждение снова либо сделать изменившееся поле совершенно очевидным и потребовать нового решения.

Для HTTP такое описание обычно включает метод, схему и хост, нормализованный путь, значения параметров запроса, байты тела или определенную каноническую форму тела, выбранные заголовки, ссылку на учетные данные и запуск агента. Не помещайте секретные значения на экран или в журнал только ради этого. Такая ссылка, как payments-production, обозначает полномочия, не раскрывая токен. Исполнитель может связать подтверждение с записью во внутреннем хранилище, которую он фактически использует.

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

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

Именно здесь команды часто предлагают разрешающее правило вроде «одинаковый адрес назначения и тот же глагол вполне подходят». Оно популярно, потому что быстро сокращает число запросов и делает демонстрации плавнее. Но оно ошибочно: HTTP-глаголы описывают широкие классы действий, а не последствия. POST не является категорией разрешения, как и ssh.

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

Повторение не доказывает безопасность вызова

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

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

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

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

Используйте в модели отдельные состояния:

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

Не сводите эти состояния к зеленому значку. Подтверждение фиксирует намерение. Результат передачи фиксирует свидетельство доставки. Ответ удаленной стороны фиксирует исход. Это разные вопросы, и запись аудита должна сохранять все три.

Тела запросов нужно проверять по смыслу и связывать по байтам

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

С JSON это непросто, потому что внешний вид и смысл могут различаться. Порядок полей объекта часто не меняет интерпретацию получателя, тогда как порядок элементов массива может изменить ее полностью. Пробелы обычно не важны, но пробел внутри строки важен. Сервис с неточным или особым разбором может по-разному обработать число, записанное как 1 и 1.0. Не придумывайте универсальный нормализатор JSON и не предполагайте, что он сохраняет смысл.

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

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

{
  "title": "Release request",
  "method": "POST",
  "url": "https://api.example.test/v1/releases",
  "headers": {"Content-Type": "application/json"},
  "body": {"version": "2.4.1", "state": "draft"}
}
{
  "title": "Release request",
  "method": "POST",
  "url": "https://api.example.test/v1/releases",
  "headers": {"Content-Type": "application/json"},
  "body": {"version": "2.4.1", "state": "published"}
}

В тесте нужно проверить больше, чем fingerprintA != fingerprintB. Убедитесь, что отрисованная карточка отмечает изменение state, что подтверждение для фикстуры с черновиком не проходит для фикстуры с опубликованным релизом, а запись активности сохраняет дайджест тела и сводку изменений на уровне полей. Последняя проверка выявляет распространенное неудачное исправление: инженеры чинят проверку авторизации, но оставляют проверяющим и расследующим бесполезный журнал.

Для двоичных тел и тел форм нужно соблюдать ту же дисциплину. При загрузке multipart могут сохраниться имя файла и тип содержимого, а сам загруженный документ замениться. В кодированной форме можно изменить role=user на role=admin в поле, которое компактная карточка никогда не показывает. Если удаленное действие важно, важно и тело.

Заголовки и учетные данные создают скрытые изменения области доступа

Сохраняйте свидетельства по каждому вызову
Журнал действий отдельно фиксирует каждый вызов и сам запуск агента.

Учетные данные, которыми отправляют запрос, входят в состав действия, даже если URL и тело полностью совпадают по байтам. Карточка с надписью «Обновить счет», скрывающая, используются ли тестовые или рабочие учетные данные, просит проверяющего одобрить действие вслепую.

Никогда не показывайте bearer-токен, пароль, закрытый ключ или пользовательский секретный заголовок. Вместо этого присвойте каждой записи секрета стабильную понятную человеку метку и покажите эту метку, класс учетной записи и предупреждение о разрешениях, которое оператор настроил сознательно. Проверяющему может не понадобиться текст токена, но он должен знать, что billing-read сменился на billing-admin или что действие теперь выполняется от имени другого SSH-пользователя.

Пользовательские заголовки вызывают самые неприятные сюрпризы, потому что часто выглядят как технические детали передачи. Значение X-Organization может перенаправить знакомый запрос в другую организацию. Заголовок X-Mode: live может перевести действие из симуляции в настоящую операцию. Idempotency-Key может определить, сочтет ли получатель запрос повтором или новой инструкцией. Показывайте эти поля в сравнении, если они меняют область действия.

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

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

Сессии показывают, кто повторяет запрос

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

Полномочия на подпись кода полезны, потому что процесс может заявить любое удобное имя. Но это не делает все его запросы одинаковыми. Доверенный процесс может выполнить измененный запрос, прочитать отравленный файл репозитория или последовать инструкции, которой оператор не давал. Идентичность процесса отвечает на вопрос «кто запустил этот сеанс?». Привязка запроса отвечает на вопрос «что сделает этот сеанс?». Нужны оба ответа.

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

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

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

Для SSH-команд нужен план выполнения, а не красивая строка

Храните API-секреты внутри системы
HTTP-учетные данные внедряются внутри Sallyport, а агент не получает токены типа bearer.

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

Если исполнитель принимает массив аргументов напрямую, сохраняйте именно такой формат. Показывайте исполняемый файл и каждый аргумент отдельными полями, а затем добавляйте разрешенную рабочую директорию, хост, удаленного пользователя и метку учетных данных. Если приходится вызывать оболочку, явно укажите это и покажите точную программу оболочки и байты скрипта. Карточка, которая красиво форматирует содержимое sh -c, но скрывает сам sh -c, пропускает самую важную часть.

Эти команды выглядят связанными, но разрешают принципиально разные действия:

ssh [email protected] 'systemctl status web'
ssh [email protected] 'systemctl restart web'

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

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

Проверяйте границу с помощью пар-коллизий

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

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

Измененное измерениеПара для тестаОжидаемый результат
Телополе черновика и поле публикацииНовое подтверждение и видимое различие в теле
Заголовокодно значение организации и другоеНовое подтверждение и отображение организации
Учетные данныетестовая и рабочая записиНовое подтверждение и отображение метки учетных данных
Сессиятот же запрос из нового процессаНовое подтверждение запуска
Контекст командыте же аргументы с другой рабочей директориейНовое подтверждение и отображение директории

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

Затем добавьте мутационные тесты. Удаляйте каждое поле из канонической идентичности в отдельной тестовой ветке и проверяйте, что тест на коллизию завершается ошибкой. Это может показаться излишней педантичностью, пока кто-то не изменит объект запроса и незаметно не перестанет передавать заголовок, рабочую директорию или метку учетных данных адаптеру подтверждений. Тест должен сразу привлечь внимание к такому пропуску.

Путь действий Sallyport хранит секреты в зашифрованном хранилище и возвращает агенту результаты, а не сами секретные данные. Это упрощает тестирование: фикстура может ссылаться на записи учетных данных по метке, а тест проверяет, что агент никогда не получает настоящие учетные данные.

Правдоподобный сбой начинается с безобидного первого вызова

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

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

Позже инструкция в репозитории говорит агенту «завершить релиз». Агент меняет только state с draft на published и добавляет заголовок, выбирающий рабочую организацию. Если интерфейс показывает только заголовок, хост и метод, последняя карточка выглядит так же, как предыдущие. Если кэш подтверждений объединяет вызовы по заголовку и адресу назначения, система может даже не запросить решение повторно.

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

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

Трение должно появляться из-за важных различий

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

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

Если вы нашли группу действий, которые действительно могут использовать одно подтверждение, зафиксируйте утверждение об их эквивалентности в коде и тестах. Например, идемпотентный повтор может нести неизменяемый идентификатор операции, для которого удаленный сервис гарантирует однократное выполнение. Это узкое утверждение с доказательствами. «Они выглядят похоже в интерфейсе» доказательством не является.

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

Вопросы и ответы

Почему два запроса на подтверждение могут выглядеть одинаково?

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

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

Используйте каноническое представление реально выполняемого запроса, а не заголовок карточки. В него входят метод, нормализованный адрес назначения, дайджест тела, идентификатор учетных данных, важные заголовки, идентификатор сессии и канал выполнения.

Безопасно ли автоматически подтверждать второй идентичный API-вызов?

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

Какие HTTP-заголовки должны отображаться в запросе на подтверждение?

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

Может ли хеш запроса заменить подробности запроса в карточке подтверждения?

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

Почему сессия агента важна для подтверждений?

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

Как тестировать интерфейс подтверждений для AI-агентов?

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

Что человек должен увидеть перед подтверждением SSH-команды?

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

Почему слишком частые запросы на подтверждение опасны?

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

Что должен сохранять журнал аудита для подтвержденного действия?

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

Sallyport

Sallyport выполняет API-вызовы и SSH-команды за вашего ИИ-агента. Ключи остаются в локальном хранилище на вашем Mac; вы подтверждаете каждый запуск, и каждое действие попадает в запечатанный журнал.

© 2026 Sallyport · Открытый код по лицензии Apache-2.0 · Oleg Sotnikov