Как банки cookies агентов сохраняют сессии, которые вы не планировали
Банки cookies агентов могут незаметно сохранять HTTP-сессии. Узнайте, как тестировать сохранение Set-Cookie, перенаправления, смену учётных данных и границы запусков.

HTTP-клиенты создают впечатление, что cookies безобидны: браузеры приучили нас к этому. У автономного агента последствия другие. Ответ с Set-Cookie может дать следующему запросу полномочия, которых агент не запрашивал, не показывал и способен сохранить после смены исходных учётных данных.
Считайте банку cookies хранилищем аутентификации, а не деталью передачи данных. Если API требует сессии на cookies, у банки должен быть именованный владелец, короткий срок жизни и наблюдаемая граница. Если не требует, отключите её. Я видел слишком много расследований инцидентов, начинавшихся со слов, что в запросе не было учётных данных, хотя клиент тихо отправил их в заголовке Cookie.
Заголовок Set-Cookie может стать вторыми учётными данными
Сервер отправляет Set-Cookie в ответе; клиент с банкой может передать это значение в следующем заголовке Cookie. Когда значение определяет серверную сессию, на практике это учётные данные. То, что оно пришло после аутентифицированного запроса, не делает его менее способным авторизовать следующий.
Команды часто упускают это из виду, потому что сосредотачиваются на секрете, который передали намеренно: bearer-токене, пароле basic-auth или подписанном запросе. Они проверяют, откуда берётся этот секрет и попадает ли он к агенту. Затем HTTP-библиотека принимает cookie сессии без единой явной строки прикладного кода. Следующий запрос может сработать уже после исчезновения исходного заголовка Authorization.
Важно различать подстановку учётных данных и продолжение сессии. Подстановка прикрепляет известный секрет к одному запросу. Продолжение сессии позволяет серверу выдать новый секрет и просит клиента повторно передать его позже. Оба способа могут авторизовать действия. В аргументах вызова инструмента обычно виден лишь один из них.
RFC 6265 описывает этот обмен как управление состоянием: пользовательский агент хранит сведения о cookie из Set-Cookie и возвращает подходящие cookies в Cookie. Такая формулировка намеренно широка, потому что она нужна веб-браузерам. Клиент агента не должен перенимать привычку браузера хранить состояние лишь потому, что HTTP-протокол это допускает.
Cookie меняет и смысл неудачной ротации токена. Допустим, агент вызывает API с токеном, получает sid=..., а позже теряет доступ к этому токену. Если API принимает cookie сессии саму по себе, у агента всё ещё остаётся путь к аккаунту. Ротация исправила известные учётные данные, но не завершила выданную сессию. Это вопрос управления серверными сессиями и ограничения на стороне клиента. Нужно решить оба.
Клиент решает, существует ли скрытое состояние
Сам по себе Set-Cookie ничего не делает. Клиент должен выбрать, сохранять ли его. Это решение может скрываться во множестве мест: в повторно используемом HTTP-клиенте, менеджере cookies библиотеки, обёртке над fetch, тестовом наборе или реализации перенаправлений.
Некоторые распространённые клиенты не сохраняют cookies, пока вы не передадите им банку. Другие сохраняют их, когда код подключает стандартный обработчик cookies. Тогда общий клиент может случайно сделать общей и банку. Не делайте выводов из документации API или из того, что вызов завершился успешно. Изучите создание клиента и проведите проверку.
Для чистого теста используйте подконтрольный сервер или безвредную тестовую конечную точку. Первый ответ должен установить cookie с понятным именем, а второй запрос должен сообщить полученные заголовки. Результат должен ответить на два отдельных вопроса:
- Сохранил ли клиент cookie после получения
Set-Cookie? - Отправил ли клиент эту cookie в следующем подходящем запросе?
В curl явный файл cookies делает состояние заметным:
curl -i -c /tmp/agent-cookie-test.txt https://test.example/session/start
HTTP/1.1 200 OK
Set-Cookie: agent_probe=run-7f3; Path=/; Secure; HttpOnly
curl -i -b /tmp/agent-cookie-test.txt https://test.example/session/echo
HTTP/1.1 200 OK
{"received_cookie":"agent_probe=run-7f3"}
Весь смысл упражнения в файле. Первая команда не может создать скрытую сессию, если ничто не сохраняет её ответ. Вторая не может повторно передать её, если не получит файл. Если инструментарий агента даёт аналогу такого файла безымянное общепроцессное место, вы создали границу сессии, которую никто не сможет осмысленно проверить.
Повторите проверку в точном пути выполнения, которым пользуется агент, включая его обёртку запросов и настройки перенаправлений. Прямой тест curl доказывает поведение curl, а не вашего инструмента. Зафиксируйте, пуста ли банка в начале, где она находится и что её очищает.
Повторное использование между вызовами отличается от повторного использования между запусками
Нескольким вызовам внутри одной задачи может понадобиться общая сессия. Повторное использование этой сессии в следующей задаче требует отдельного решения. Совмещение этих сроков жизни превращает небольшое удобство в постоянные полномочия.
Оценивая поведение, используйте три границы. Сначала спросите, нужны ли cookies одному запросу вообще. Затем спросите, нужны ли они связанным вызовам в одном процессе агента. Потом спросите, должен ли их когда-либо наследовать новый процесс, возобновлённая задача, другие учётные данные или другой человек. Для каждого ответа «да» нужна явная причина.
Банку в памяти, исчезающую вместе с процессом, проще ограничить, чем файл в каталоге проекта. Файл может пережить сбой, повторную попытку, копирование рабочей области или смену исполнителя задачи. Он также может попасть в архив поддержки или вывод статуса системы контроля версий. HttpOnly не защищает файл cookies от клиента, который его записал: он лишь ограничивает доступ из браузерных скриптов.
Я предпочитаю новую банку для каждого запуска агента, даже если та же модель получает следующий промпт. Непрерывность разговора модели не причина сохранять HTTP-полномочия. Если следующему запросу действительно нужна сессия, оператор должен одобрить продолжение запуска в той же именованной границе, а не молча оставить после прошлого запуска пригодную сессию.
Для параллельных вызовов тоже нужно отдельное решение. Общая банка может создать поведение, зависящее от порядка: запрос A получает cookie, запрос B начинается мгновениями позже, и B получает сессию, которую не создавал. Воспроизводить такое очень тяжело. Выдавайте параллельным задачам отдельные банки, если API не требует скоординированной сессии и задача явно ею не владеет.
Область действия cookie не соответствует ожидаемой безопасности
Атрибуты cookies ограничивают доставку, но не делают сессию безвредной. Считайте их инструкциями маршрутизации от сервера клиенту, а затем решайте, должен ли ваш клиент вообще им следовать.
Cookie только для хоста возвращается ровно тому хосту, который её выдал. Cookie с Domain=example.com может вернуться подходящим поддоменам, например api.example.com и admin.example.com. RFC 6265 также говорит, что пользовательский агент отклоняет значение Domain, не соответствующее домену исходного хоста, но это не решает обычную ошибку: доверять каждому поддомену большого корпоративного домена.
Path=/billing ограничивает cookie запросами, пути которых соответствуют пути cookie. Это не мешает серверу на другом разрешённом пути принять ту же cookie, если она пришла к нему иным маршрутом, и не заменяет проверки авторизации. Не называйте Path границей безопасности в обсуждениях архитектуры. Это правило отправки на стороне клиента.
Secure говорит клиенту отправлять cookie только по защищённому соединению. HttpOnly говорит браузеру не показывать её через API скриптов. Оба атрибута полезны для базовой гигиены, но ни один не ограничивает хранение, совместное использование между задачами, перенаправления или способность агента применять cookie. SameSite главным образом регулирует контекст сайтов в браузере; агент без браузера не должен считать его доказательством безопасности межсайтового поведения.
Есть и другая распространённая ошибка: записывать исходные заголовки при отладке. Редактор, который удаляет Authorization, но оставляет Cookie, не скрывает аутентификацию. Записывайте наличие cookie, её имя, объявленные Domain и Path, тип срока действия и необратимый идентификатор для сопоставления, если он нужен. Не записывайте значение.
Перенаправления превращают проверку cookies в проверку получателя
Перенаправление - это не просто другой URL. Оно может изменить, какой сервер увидит следующий запрос, сохранятся ли учётные данные и какой ответ сможет установить состояние. Проверяйте его как отдельный поток.
Начните с конечной точки, которая возвращает перенаправление после установки cookie. Затем отдельно проверьте каждое назначение: тот же хост, разрешённый поддомен, соседний поддомен и несвязанный хост. Отслеживайте и исходящий Cookie, и исходящий Authorization. Разные HTTP-стеки принимают разные решения, а обёртка может переопределить значения по умолчанию.
Полезный тестовый сценарий выдаёт короткую трассировку вроде этой:
request 1 GET https://api.example.test/start
response 1 302 Location: https://api.example.test/next
Set-Cookie: probe=A; Path=/; Secure
request 2 GET https://api.example.test/next
Cookie: probe=A
response 2 200
Затем измените только хост Location. Если api.example.test перенаправляет на reports.example.test, cookie только для хоста не должна последовать за ним. Cookie с областью Domain может последовать. До запуска теста укажите ожидаемый результат: неожиданная трассировка и есть цель проверки, а не неудобство, которое нужно замаскировать.
Отклоняйте перенаправления между источниками, если контракт API их не требует. Если следовать им необходимо, сравнивайте старый и новый источники, удаляйте учётные данные запроса по явному правилу и позволяйте свежему ответу установить новое состояние cookies. Не используйте широкую автоматическую политику перенаправлений, рассчитывая, что область cookies вас спасёт.
Тест сессии должен пересекать границы учётных данных и процессов
Тест, который ловит дорогую ошибку, не сводится к «запрос один, запрос два». Он пересекает границы, которые, как утверждает ваша операционная модель, она соблюдает.
Создайте безвредную конечную точку, которая выдаёт cookie только после получения выбранной метки учётных данных. Пусть она возвращает метку сессии в каждом авторизованном запросе. Затем выполните такую последовательность:
- Запустите запуск A с учётными данными A и получите
sid=A. - Сделайте второй вызов без учётных данных A, но с той же банкой.
- Запустите запуск B с учётными данными B и пустой банкой.
- Запустите запуск C без учётных данных и с любой сохранённой банкой из запуска A.
- Отзовите учётные данные A или сделайте их сессию недействительной на тестовом сервере, затем повторите попытку с банкой запуска A.
Ожидаемый результат - это решение политики, а не универсальный ответ. API на cookies может намеренно разрешать второй вызов в рамках запуска A. Запуск B не должен видеть состояние A. Запуск C должен завершиться неудачей, если вы явно не одобрили постоянное хранение. После отзыва тестовый сервер должен отклонить старую сессию, если по вашей модели угроз ротация токена должна убрать активный доступ.
Запишите ожидание рядом с тестом, а не храните его только в памяти разработчика. Подойдёт краткая таблица в комментарии к тесту:
run A, same jar, no bearer header: allowed only if session continuation is intended
run B, fresh jar, credential B: must identify as B
run C, persisted jar, no credential: rejected
run A after session invalidation: rejected
Так проявляется различие, которое команды часто смешивают: отзыв начальных учётных данных и отзыв выданных сессий - не одна операция. Владелец API должен аннулировать сессии. Владелец инструмента агента должен не сохранять их дольше одобренного срока. Ни одна сторона не может считать, что это сделала другая.
Отключайте неявные банки, если API не нуждается в них
Разумное значение по умолчанию для HTTP-действия агента - не хранить cookies и не добавлять заголовок Cookie автоматически. Ответ всё ещё может содержать Set-Cookie: зафиксируйте это, если позволяет ваша модель аудита, а затем отбросьте значение. В следующем вызове API должен использовать обычные учётные данные запроса.
Эта рекомендация непопулярна, потому что многие API, близкие к вебу, работают после точки входа лишь тогда, когда клиент сохраняет cookie. Люди выбирают общую банку, потому что с ней проходят демонстрации и интеграционные тесты. Это неверное решение, когда документированный API поддерживает bearer-токены или другой механизм в рамках одного запроса. Вы сохраняете невидимые полномочия, чтобы компенсировать путь интеграции, который выбирать не следовало.
Если API действительно требует cookies, сделайте банку явной возможностью. В начале запуска вызывающая сторона должна выбрать именованную пустую банку, инструмент должен сообщать, когда ответ создаёт или заменяет cookie, а банка должна исчезать в конце. Не позволяйте конечной точке подключать каждого агента к постоянной сессии лишь возвратом заголовка.
Минимальная политика может быть достаточно простой для проверки:
cookie mode: disabled by default
allowed mode: ephemeral per agent run
persistence: prohibited
sharing: prohibited between agent processes
redirects: same-origin only unless the action definition permits another origin
logging: record cookie names and scope, never values
Она намеренно проще механизма правил. Умная политика cookies обрастает исключениями, пока никто не сможет сказать, какое действие какое состояние переносит. Небольшой набор решений даёт проверяющим ясный ответ.
Проверяйте переход состояния, а не только запрос
Аудит, перечисляющий URL и коды статуса, упустит самое полезное событие: ответ изменил возможности последующих вызовов. Фиксируйте изменения состояния cookies как самостоятельные события, не сохраняя сам секрет сессии.
Для каждого HTTP-действия запись должна позволять ответить на вопросы: отправлял ли запрос cookies, устанавливал или очищал ли ответ какие-либо cookies, какая банка их получила, принадлежала ли она этому запуску и было ли перенаправление? Имен cookies и их атрибутов обычно достаточно для диагностики. Храните значения только при убедительной архитектуре защиты и истечения срока, в которой большинство инструментов агентов не нуждается.
Когда агент использует Sallyport для HTTP-действий, хранилище может не допускать настроенные учётные данные API к агенту во время выполнения действия. Такое разделение полезно лишь тогда, когда HTTP-клиент относится к любому возвращённому состоянию сессии с той же осторожностью, а не тихо превращает его в ещё один путь учётных данных.
В аудит-записях также должна быть понятная человеку граница запуска. Если оператор отзывает запуск агента, он должен знать, предотвращает ли это будущие вызовы только через настроенные учётные данные или также очищает состояние сессии, привязанное к запуску. Если не очищает, скажите об этом прямо и сделайте оставшееся состояние недоступным для следующего процесса.
Первый тест, который я бы добавил, намеренно скучный: одна конечная точка отправляет Set-Cookie, следующая подтверждает, пришла ли она, а новый процесс агента повторяет вызов. Запустите его до добавления повторных попыток, перенаправлений, совместимости с браузерами или постоянного кеша. Если ответ не очевиден из трассировки, у клиента больше полномочий, чем признаёт его интерфейс.
Авторы API могут сделать агентов безопаснее без догадок
Авторам API следует документировать, требуются ли cookies, что их создаёт, каков их срок жизни и как клиентам их аннулировать. Фразы «используйте эту конечную точку после входа» недостаточно, когда вход может выдать сессию, которая живёт дольше учётных данных, использованных для её получения.
По возможности предложите вариант в рамках одного запроса. Bearer-аутентификация, подписанные запросы или узко ограниченный токен действия часто упрощают аудит поведения клиента, потому что каждый вызов явно несёт свои полномочия. Это не делает такие механизмы автоматически безопасными, но устраняет дополнительный канал повторной передачи, скрытый в памяти клиента.
Если вы выдаёте cookie сессии, обеспечьте аннулирование сессий и протестируйте его. Ротация токена без аннулирования сессий оставляет операторам неприятное ложное ощущение завершённости. Если вы принимаете и bearer-токен, и cookie сессии, определите, какой из них имеет приоритет при расхождении, и покажите это решение в диагностическом выводе.
Не советуйте разработчикам агентов имитировать браузеры, если сервис действительно не зависит от поведения браузера. Агенты многократно выполняют вызовы без наблюдения и часто работают с разными задачами. Удобное долгоживущее состояние браузера - плохой вариант по умолчанию для такой среды.
Безопасный вариант по умолчанию: новый клиент без запомненной сессии
Поддержка cookies не плоха, плохо состояние без владельца. Короткоживущая явно выбранная банка может быть правильным способом завершить задачу API с несколькими вызовами. Банка, появившаяся из-за повторного использования HTTP-клиента, ждёт подходящей повторной попытки, перенаправления или ротации учётных данных.
Сделайте границу сессии видимой в интерфейсе инструмента и в аудит-записи. Затем докажите её тестом между запусками. По трассировке запроса проверяющий должен суметь указать каждый путь учётных данных, который был у агента, включая те, которые сервер пытался выдать в ответ.
Вопросы и ответы
Что такое банка cookies в HTTP-клиенте?
Банка cookies - это клиентское хранилище, которое запоминает cookies из HTTP-ответов и решает, в каких следующих запросах отправить их обратно. В инструменте агента эта память может превратить отдельные вызовы в одну аутентифицированную сессию, похожую на браузерную.
Стоит ли ИИ-агенту сохранять cookies между вызовами API?
Это допустимо, если API использует cookies как предусмотренный механизм сессии и в рамках запуска нужно выполнить несколько связанных вызовов. Но это плохой вариант по умолчанию, когда у агента есть bearer-токены, подписанные запросы или отдельные границы задач: cookie создаёт полномочия, которые трудно увидеть в промпте.
Может ли cookie сессии пережить ротацию учётных данных?
Cookie может пережить учётные данные, после которых сервер её выдал. Если сервер принимает cookie сессии без повторной проверки bearer-токена, удаление или ротация токена не завершит уже выданную сессию.
HTTP-клиенты автоматически сохраняют заголовки Set-Cookie?
Не делайте такого предположения. Проверьте это на конечной точке, которая записывает входящий заголовок Cookie: клиенты различаются. Одни сохраняют cookies только в явно заданной банке, другие добавляют хранилище cookies через общий клиент, обёртку или обработчик перенаправлений.
Как долго должна существовать cookie-сессия агента?
Самая безопасная граница - новая банка cookies для каждого процесса агента или для явно определённой задачи. Очищайте её после завершения, не записывайте в рабочую область для повторного использования и принимайте осознанное решение, прежде чем передавать её другому запуску.
Что означают Domain и Path в HTTP-cookie?
Cookie только для хоста возвращается лишь тому хосту, который её установил, а атрибут Domain может сделать её доступной для поддоменов. Атрибут Path сужает область, в которой клиент её отправляет, но это правило маршрутизации, а не граница контроля доступа.
Делает ли атрибут Secure сессии агента безопасными?
Нет. Secure означает, что клиент должен отправлять cookie только по HTTPS. Этот атрибут ничего не говорит о том, должен ли агент её хранить, передавать другим или считать заменой другим учётным данным.
Могут ли перенаправления привести к утечке cookies?
Перенаправления могут привести клиента к хосту, который установит cookie, или к хосту, который её получит, в зависимости от банки и области действия cookie. Проверяйте перенаправления отдельно и отклоняйте переходы между источниками, когда API в них не нуждается.
Что записывать о cookies в журнал, не раскрывая секреты?
Записывайте адрес назначения запроса, сам факт отправки или получения cookies, имена cookies, их область действия и идентификатор банки или границу запуска. Не записывайте значения cookies, потому что они часто служат учётными данными сессии.
Как предотвратить скрытые сессии в HTTP-инструментах агентов?
Разделяйте обычную подстановку учётных данных и банку cookies и в коде, и при проверке. Дайте банке короткий срок жизни, явно определите правила её совместного использования и очищайте после завершения запуска. Если считать её обычным удобством HTTP, решение об аутентификации станет скрытым.