# Должна ли возобновленная сессия агента сохранять старое разрешение?

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

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

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

## Возобновленный разговор не несет полномочий

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

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

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

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

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

Model Context Protocol помогает яснее увидеть это различие. В документации транспорта сказано, что при использовании stdio клиент запускает сервер как дочерний процесс и обменивается сообщениями JSON-RPC через стандартные потоки ввода и вывода. Это живая связь между процессами, а не постоянное разрешение, сохраненное в расшифровке. В журнале изменений MCP также были удалены сессии на уровне протокола из более новых вариантов Streamable HTTP, а состоянием серверов теперь предлагается управлять через явные дескрипторы, создаваемые сервером. Само по себе это не решает задачу авторизации, зато не позволяет выдавать идентификатор транспортной сессии за учетные данные идентичности.

В проекте важно не смешивать термины:

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

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

## Идентичность процесса состоит из нескольких частей

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

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

Но не превращайте полномочия подписи в универсальный ответ. Два отдельных процесса с одинаковым designated requirement все равно остаются двумя процессами. Подписанный агент может запустить неподписанный помощник. Локально собранный бинарный файл для разработки может иметь специальную ad hoc-подпись. Если злоумышленник получил контроль над одобренным процессом после выдачи разрешения, исходная правильная подпись не делает его безопасным.

Связывайте процесс с набором фактов, которые имеют смысл вместе:

```text
approval_subject = {
  process_id: 48192,
  process_start_time: "2026-07-22T14:18:03Z",
  executable_file_id: "volume:.../inode:...",
  executable_hash: "sha256:...",
  signing_requirement: "anchor ... and identifier ...",
  parent_process_id: 48001,
  launch_nonce: "random-128-bit-value"
}
```

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

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

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

## Перезапуск процесса завершает разрешение на сессию

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

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

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

Не пытайтесь исправить это большим сроком действия. Разрешение на пять минут, которое может возобновить любой процесс-замена, не является разрешением на сессию сроком пять минут. Это bearer-учетные данные на пять минут с дружелюбной подписью.

Лучше использовать простое правило:

```text
if current.process_id != approved.process_id:
    deny("approval belongs to a different process")

if current.process_start_time != approved.process_start_time:
    deny("process lifetime changed")

if current.launch_nonce != approved.launch_nonce:
    deny("gateway has not bound this run")
```

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

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

## Изменение состояния хранилища лишает права действовать

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

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

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

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

```text
approved_vault_epoch = 17
current_vault_epoch = 18

if approved_vault_epoch != current_vault_epoch:
    require_new_session_approval()
```

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

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

## Перезапуск шлюза стирает его память о разрешении

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

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

Такое решение также делает восстановление неоднозначным. Упал ли шлюз до отправки действия? Отправил ли он действие и упал до записи ответа? Получил ли удаленный сервис запрос дважды после повтора? Восстановление авторизации и повторную отправку запроса нельзя объединять в одну операцию. Для них нужны разные средства контроля.

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

```text
approval = {
  gateway_boot_id: "b7f9...",
  process_binding: "...",
  vault_epoch: 17,
  approved_at: "2026-07-22T14:20:11Z"
}

if approval.gateway_boot_id != gateway.current_boot_id:
    require_new_session_approval()
```

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

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

## Привязывайте разрешение к свидетельствам, которые клиент не может воспроизвести

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

Минимальная практичная схема использует четыре меняющихся значения:

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

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

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

```json
{
  "run": {
    "conversation_ref": "worktree-cleanup-42",
    "process": {
      "pid": 48192,
      "started_at": "2026-07-22T14:18:03Z",
      "signing_requirement": "recorded-at-approval",
      "launch_nonce": "gateway-generated"
    }
  },
  "approval": {
    "id": "gateway-generated",
    "gateway_boot_id": "gateway-generated",
    "vault_epoch": 17,
    "expires_when_process_exits": true
  }
}
```

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

При переподключении агент должен повторить инициализацию и регистрацию процесса. После этого шлюз должен вернуть один из следующих результатов:

```text
REAUTH_REQUIRED process_changed
REAUTH_REQUIRED gateway_restarted
REAUTH_REQUIRED vault_state_changed
RETRY_SAFE previous_action_not_started
STATUS_UNKNOWN inspect_activity_journal
```

Различие между `RETRY_SAFE` и `STATUS_UNKNOWN` важно. Шлюз может сказать, что действие не началось, только если у него есть надежные долговременные свидетельства этого. Если питание пропало после передачи HTTP-запроса сетевому стеку, честным ответом может быть неизвестный статус. Перед повтором агент должен проверить назначение или журнал, чтобы не выполнить действие дважды.

## Повтор действия не связан с восстановлением разрешения

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

Для защищенного действия, связь с которым потеряна, используйте такую последовательность:

1. Агент подключается снова и устанавливает новые отношения со шлюзом.
2. Шлюз определяет текущий процесс и сравнивает его с текущей записью запуска.
3. Шлюз проверяет идентификатор загрузки и текущую эпоху хранилища.
4. Если изменился любой факт, связанный с полномочиями, шлюз запрашивает новое разрешение на сессию до использования учетных данных.
5. Шлюз сообщает, известно ли ему, что прежнее действие не началось, завершилось или имеет неизвестный статус.

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

Для HTTP API используйте ключ идемпотентности, если его поддерживает назначение. Создайте ключ для логического бизнес-действия, надежно сохраните его до отправки и используйте повторно только после восстановления авторизации для повтора. Ключ идемпотентности помогает удаленному сервису обнаружить повторную доставку. Он не авторизует повтор.

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

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

## Некрасивые сбои показывают правильную границу

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

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

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

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

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

До объявления поведения возобновления безопасным проверьте как минимум следующие случаи:

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

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

## Журналы должны объяснять, почему полномочия закончились

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

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

Полезная последовательность активности выглядит так:

```text
14:20:11 session_approved run=R31 signer="Example Developer ID" boot=B8 vault=17
14:23:04 gateway_restarted previous_boot=B8 current_boot=C2
14:23:06 action_denied run=R31 reason=gateway_restarted
14:23:09 session_approved run=R32 signer="Example Developer ID" boot=C2 vault=17
14:23:12 http_action_started run=R32 request=Q44
14:23:13 http_action_result run=R32 request=Q44 status=201
```

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

Здесь важны свидетельства неизменности, потому что история разрешений полезна только тогда, когда можно обнаружить ее последующее редактирование. Sallyport строит журналы Sessions и Activity из защищенного от записи зашифрованного журнала аудита с цепочкой хешей, а `sp audit verify` может автономно проверять эту цепочку по шифротексту без ключа хранилища. Проверяющий может установить последовательность событий, не открывая секреты, с помощью которых выполнялись действия.

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

## Повторное согласие должно быть достаточно конкретным

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

Избегайте общего сообщения вроде «Срок действия сессии истек. Одобрить снова?». Оно превращает событие в обычный таймер. Покажите настоящее нарушение непрерывности: «Шлюз перезапустился. Разрешить этому вновь подключенному процессу использовать API Git-хостинга в рамках этого запуска?». Если изменилась идентичность процесса, покажите нового подписавшего или укажите, что программа не подписана. Если хранилище было заблокировано, скажите, что его разблокировка не восстановила прежнее разрешение агента.

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

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

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