# Общие Mac разработчиков: контроль доступа агентов к инструментам

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

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

## Общим машинам нужны отдельные идентичности

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

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

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

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

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

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

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

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

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

## Учетная запись Unix не изолирует скопированный секрет

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

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

```sh
# Search filenames and likely configuration references, not secret values.
find "$HOME" -maxdepth 3 \( -name '.env' -o -name '.netrc' -o -name 'credentials*' \) -print

# Show environment variable names for the current shell only.
env | cut -d= -f1 | grep -E 'TOKEN|SECRET|KEY|PASSWORD' || true

# Show loaded SSH identities without printing private key material.
ssh-add -l
```

Последняя команда обычно выводит по одной строке на каждую идентичность: отпечаток, алгоритм и комментарий. Если она сообщает `The agent has no identities.`, это полезный результат. Если вы видите отпечаток, который не можете объяснить, перестаньте считать машину чистой и выясните, какой рабочий процесс загрузил этот ключ.

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

Часто дают совет: «Положите секреты в локальный файл .env и добавьте его в исключения Git». Он популярен, потому что работает за пять минут. Но тогда любая программа, запущенная из этого каталога, потенциально получает доступ к учетным данным. Запись в `.gitignore` предотвращает коммит, но не мешает локальному агенту прочитать файл, архиватору включить его в архив или разработчику скопировать его в следующий проект.

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

К наследованию процессов нужно относиться так же внимательно. Терминал экспортирует `DEPLOY_TOKEN`, редактор запускается из этого терминала, расширение запускает сервер языка, а агент вызывает инструмент через редактор. Разработчик мог забыть о токене несколько часов назад, но каждый дочерний процесс по-прежнему способен его прочитать. Разрешения macOS сработали именно так, как было задано. Ошибка в том, что команда дала слишком многим процессам доступ к секрету.

## Хранение учетных данных и право на действие - разные вещи

Учетная запись отвечает на вопрос «кто может пройти аутентификацию?». Авторизация отвечает на вопрос «может ли этот процесс выполнить это действие сейчас?». Команды часто принимают наличие первого за доказательство второго, особенно в случае API-токенов.

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

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

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

```json
{
  "action": "create_deployment",
  "environment": "staging",
  "revision": "7c31f4a",
  "summary": "Fix request timeout handling"
}
```

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

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

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

## Локальные процессы могут получить больше полномочий, чем вы планировали

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

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

```sh
ps -axo pid,ppid,user,command | grep -i '[a]gent\|[c]laude\|[n]ode\|[p]ython'
lsof -nP -p <PID> | grep -E 'cwd|unix|TCP|IPv4|IPv6'
```

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

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

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

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

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

## К SSH-агентам нужно относиться так же внимательно, как к закрытым ключам

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

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

Перед автоматизацией работы с удаленной системой выполните:

```sh
printf '%s\n' "${SSH_AUTH_SOCK:-SSH_AUTH_SOCK is unset}"
ssh-add -l
ssh -G deploy@staging.example.internal | grep -E '^(hostname|user|identityfile|forwardagent) '
```

`ssh -G` выводит итоговую конфигурацию клиента OpenSSH. В ней будут строки вроде `user deploy`, `identityfile ...` и `forwardagent no`. Внимательно проверьте последнюю строку. Пересылка агента позволяет удаленному хосту использовать локальный агент через пересланное соединение. Она должна быть отключена, если вы не можете точно объяснить, через какой удаленный узел идет переход и зачем это нужно.

В руководстве OpenSSH `ssh_config` описаны `ForwardAgent` и предупреждение о том, что пересылка может открыть локальный агент пользователям с достаточными правами на удаленном хосте. Команды все равно часто включают ее повсюду, потому что так не нужно копировать ключи и jump host кажется удобнее. Удобство действительно есть. Но скомпрометированная или слишком открытая удаленная среда может запрашивать подписи, пока пересланная сессия существует.

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

```sh
ssh-add -d ~/.ssh/id_staging_deploy
# Or clear every identity after a short, dedicated session.
ssh-add -D
```

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

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

## Подтверждение должно называть процесс и действие

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

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

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

В карточке подтверждения простым языком должны быть ответы на вопросы:

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

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

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

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

## Журнал должен позволять установить владельца задним числом

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

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

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

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

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

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

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

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

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

1. Назначьте владельца-человека, разрешенный сервис, окружение и условие вывода из эксплуатации для каждой учетной записи.
2. Создайте для агента отдельный контракт действия вместо общего токена или shell-сессии.
3. Запустите агента из нужной учетной записи разработчика с пустым или минимальным окружением учетных данных.
4. Подтвердите новый процесс только после проверки его идентичности и назначения запроса.
5. Завершите запуск, при необходимости отзовите его и убедитесь, что вспомогательные процессы и SSH-идентичности исчезли.

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

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

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

## Проверяйте отзыв доступа, пока агент еще работает

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

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

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

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

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