# Почему файлы с OAuth refresh token считаются продакшен-учетными данными?

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

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

## Файл с токеном обновления - это пакет учетных данных

Файл с токеном обновления OAuth - это пакет учетных данных: обычно в нем достаточно состояния, чтобы получить новый bearer-токен без участия человека. Точный набор полей зависит от клиентской библиотеки, но опасная комбинация знакома: токен обновления, идентификатор клиента, конечная точка токенов, выданные области доступа и иногда секрет клиента или привязанное к устройству утверждение.

Не позволяйте расширению файла преуменьшать риск. JSON - всего лишь оболочка. Файл с именем `.cache/session.json`, `token-store.json` или `auth.json` нужно защищать так же, как закрытый SSH-ключ, если с его помощью можно обновлять доступ к рабочему сервису.

OAuth 2.0 Authorization Framework, RFC 6749, описывает токены обновления как учетные данные для получения токенов доступа. Там также сказано, что сервер авторизации может выдать новый токен обновления, а клиент должен удалить старый. Эту последнюю фразу легко списать на детали протокола. В системе без постоянного контроля это операционное требование: два рабочих процесса, каждый из которых считает свой файл актуальным, могут начать спорить за идентичность одних учетных данных.

Разделяйте в инвентаризации три сущности:

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

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

Запись в рабочем инвентаре должна отвечать не только на вопрос «какой сервис это использует?». Укажите сервер авторизации, идентификатор OAuth-клиента, сервер ресурсов, субъект или сервисную учетную запись, точные выданные области доступа, среду, время выпуска, если оно доступно, владельца обновления, утвержденный путь выполнения и способ отзыва. Если вы не можете заполнить эти поля, перед вами еще не готовые к автономному использованию учетные данные. Это файл, который случайно сработал во время тестирования.

## В работе без интерфейса легко потерять владельца

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

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

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

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

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

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

```text
Credential name: billing-export-prod
OAuth client: agent-billing-prod
Resource identity: svc-billing-export
Scopes: reports.read, exports.write
Renewal owner: platform-oncall
Execution path: production job runner through credential broker
Revocation: authorization server admin console and RFC 7009 endpoint
```

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

## Храните путь обновления вне рабочей области агента

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

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

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

```json
{
  "action": "create_export",
  "target": "billing-api",
  "parameters": {
    "report_date": "2026-07-23"
  }
}
```

Агент получает примерно такой ответ, а не токен:

```json
{
  "status": "accepted",
  "export_id": "exp_4821",
  "report_date": "2026-07-23"
}
```

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

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

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

## Области доступа должны описывать одно задание, а не отдел

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

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

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

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

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

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

## Самостоятельно обновляющимся токенам нужен один владелец состояния

Ротация токенов обновления создает проблему управления состоянием, и автономные рабочие процессы должны решать ее заранее. Многие серверы авторизации меняют токены обновления: после успешного обновления сервер возвращает замену и может сделать предыдущий токен недействительным. RFC 9700, OAuth 2.0 Security Best Current Practice, рекомендует ротацию токенов обновления или токены, привязанные к отправителю, для публичных клиентов, чтобы обнаруживать повторное использование. Это разумная рекомендация по безопасности, но она не делает общий файл безопасным.

Рассмотрим распространенный сбой. Рабочие процессы A и B запускаются с одного смонтированного `auth.json`. A обновляется первым и получает `R2`, а провайдер делает `R1` недействительным. До того как A запишет `R2`, процесс завершается или запись попадает на локальный слой, который B не видит. B отправляет `R1`, получает `invalid_grant` и повторяет попытку. Оператор видит сбой задания, копирует старый кэш из резервной копии, и теперь реагирование на инцидент создало еще несколько копий учетных данных.

Используйте один из следующих вариантов:

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

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

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

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

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

## Ротация - это инструкция, а не напоминание в календаре

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

Рабочая инструкция состоит из пяти действий:

1. Остановите затронутый агент или путь через брокер, чтобы он не продолжал обновлять токен во время изменений.
2. Определите OAuth-клиент, сервисную идентичность, области доступа и версию учетных данных по записи владельца и журналам.
3. Отзовите токен обновления или разрешение на сервере авторизации через консоль провайдера или совместимую с RFC 7009 конечную точку отзыва, если она доступна.
4. Удалите все известные рабочие копии и отключите любой процесс резервного копирования или кэширования, который может восстановить файл.
5. Повторно зарегистрируйте выделенную идентичность, проверьте минимально разрешенное действие и запишите версию замены.

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

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

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

## Ведите раздельный аудит действий и обновлений

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

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

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

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

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

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

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

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

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

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

## Рассматривайте старые файлы auth.json как отдельный проект миграции

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

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

Некоторые инструменты будут сопротивляться. Отдельные SDK считают локальный JSON-кэш своей собственностью и без предупреждения обновляют токен, когда видят этот файл. Оставьте такие инструменты в ограниченной совместимой обертке: читать файл может только один процесс, а общего доступа агента быть не должно. Включите удаление этой обертки в рабочую очередь владельца сервиса. Фраза «библиотека этого требует» объясняет временное исключение, но не оправдывает постоянный путь утечки секрета.

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

У токена обновления должны быть один ответственный владелец, один контролируемый путь обновления и журнал аудита, в котором названо каждое внешнее действие, разрешенное этим токеном. Если текущий `auth.json` не соответствует этим условиям, отзовите его после того, как новый путь докажет свою работоспособность.
