Риски пересылки SSH-агента при разработке с ИИ
Риски пересылки SSH-агента растут в рабочих процессах с ИИ. Узнайте, как удалённые хосты повторно используют полномочия подписи, как отключить пересылку и какие схемы SSH безопаснее.

Риски пересылки SSH-агента легко недооценить, потому что закрытый ключ остаётся на компьютере разработчика. Это действительно так, но этого недостаточно. Удалённый хост, получивший пересланный сокет агента, может попросить локальный агент подписать запросы на SSH-аутентификацию. Для каждой системы, принимающей эту личность, удалённый хост может действовать от вашего имени, пока сохраняется такой доступ.
ИИ-помощь в разработке упрощает создание такой ошибки и затрудняет её обнаружение. Агент для программирования может открыть удалённые оболочки, выполнить команды развёртывания, проверить репозитории и использовать конфигурацию SSH, которую вы много лет назад написали для интерактивной сессии. Если агент доберётся до машины с вашим пересланным сокетом, граница доступа уже сместилась. По умолчанию отключайте пересылку, а удалённым задачам, которым действительно нужен ещё один переход, выдавайте отдельные узкие идентичности.
Пересланный агент может выполнять аутентификацию за пределами первого хоста
Пересылка SSH-агента открывает сервис подписи, а не копирует закрытый ключ. Ваш локальный ssh-agent хранит одну или несколько закрытых идентичностей. При подключении с включённой пересылкой SSH создаёт сокет на удалённой машине и передаёт запросы на подпись по зашифрованному соединению локальному агенту.
Это различие часто приводит к неверной оценке риска. Люди слышат: «ключ никогда не покидает мой ноутбук» и делают вывод, что на удалённой машине нечего атаковать. Байты закрытого ключа действительно могут оставаться локально, но для SSH-аутентификации удалённой машине не нужно владеть этими байтами. Ей нужна действительная подпись запроса на аутентификацию. Пересланный сокет позволяет её получить.
Руководство OpenSSH ssh_config(5) прямо описывает ForwardAgent и предупреждает: пользователи, способные обойти разрешения на файлы удалённого хоста, могут получить доступ к пересланному агенту. На практике это означает, что сокетом может воспользоваться сама удалённая учётная запись, процесс от её имени, администратор или злоумышленник, получивший достаточный контроль. При наличии прав root на удалённом хосте вопрос разрешений сокета больше не имеет значения.
RFC 4252 описывает аутентификацию SSH по открытому ключу как подпись данных, связанных с конкретным соединением. Сервер проверяет эту подпись по разрешённому публичному ключу. Скомпрометированный удалённый хост не может превратить агент в универсальный сервис подписи произвольных документов, но может запросить нужные ему SSH-подписи для попыток входа на другие системы. Именно это и нужно злоумышленнику, если ваша личность работает в системе контроля исходного кода, на рабочих серверах или во внутренних административных системах.
Обычно проблема развивается так:
- Разработчик подключается к
build.example.netсssh -A, потому что этой машине нужно обратиться к закрытому репозиторию. - Локальный агент разработчика появляется на сервере сборки через
SSH_AUTH_SOCK. - Скрипт сборки, скомпрометированная зависимость или другой пользователь с достаточными правами просит этот сокет подписать запрос для
git.example.netили внутреннего хоста. - Конечная система принимает публичный ключ разработчика и записывает новое соединение как выполненное разработчиком.
SSH-сервер на конечной системе видит действительную криптографическую подпись. Он не может определить, инициировал ли соединение человек со своего ноутбука или процесс на первом удалённом хосте, запросивший подпись через пересылку. Журналы сервера покажут принятый ключ и исходный адрес, но не восстановят утраченное различие.
Наследование конфигурации создаёт случайную уязвимость
Большинство опасных случаев пересылки начинается с конфигурации SSH, а не с осознанного решения во время рискованной сессии. Одной старой секции Host * с ForwardAgent yes достаточно, чтобы правило применялось ко всем хостам, к которым обращается разработчик, терминальный инструмент или агент для программирования. Оно сработает и тогда, когда инструмент вызывает ssh из скрипта, а рядом нет человека, который увидит предупреждение.
У конфигурации OpenSSH есть ещё одна особенность: для каждого параметра клиент обычно использует первое найденное значение. Широкое правило, расположенное перед узким, может отменить исключение, которое вы считали рабочим. Не полагайтесь на быстрый просмотр ~/.ssh/config. Попросите SSH показать конфигурацию, которую он применит.
Перед подключением выполните на локальной машине:
ssh -G deploy.internal.example | grep '^forwardagent '
Безопасный результат выглядит так:
forwardagent no
ssh -G выводит итоговую конфигурацию клиента после обработки файлов и параметров командной строки. Соединение он не открывает. Поэтому команда подходит для проверок при настройке и для тестового скрипта, который читает список чувствительных хостов.
Задайте явный запрет по умолчанию ближе к концу нужного файла конфигурации. Узкие исключения разместите перед ним, потому что OpenSSH использует первое совпавшее значение:
Host docs-bastion.example
ForwardAgent yes
Host *
ForwardAgent no
В этом примере всё ещё есть опасное исключение, поэтому ему нужны понятная причина и ответственный. Смысл настройки в том, чтобы исключение было заметным и относилось к одному хосту, а не незаметно распространялось на каждую новую среду.
Для разового подключения принудительно задайте безопасную настройку, даже если файл конфигурации говорит обратное:
ssh -o ForwardAgent=no [email protected]
Короткая команда ssh -a [email protected] делает то же самое. Используйте любой из вариантов при подключении к непроверенной машине, временному хосту поддержки, учебной среде или системе поставщика.
Не принимайте IdentitiesOnly yes за настройку управления пересылкой. Она определяет, какие идентичности SSH-клиент предлагает серверу при аутентификации. Она не мешает SSH создать сокет агента на удалённой стороне. Точно так же удаление SSH_AUTH_SOCK из одной оболочки не задаёт полноценную политику. Дочерний процесс может унаследовать другое окружение, а конфигурация SSH снова включит пересылку при следующем подключении.
ИИ-сценарии увеличивают число путей, которые нужно проверять
ИИ-агенту для программирования не нужны злые намерения, чтобы пересылка стала опасной. Достаточно разрешения выполнить команду, которая столкнётся с уже существующими привычками работы с SSH. Агенты выполняют инструкции из репозитория, запускают скрипты сборки, используют удалённые среды разработки и повторяют команды с небольшими изменениями. Это обычное поведение. Риск появляется, когда среда даёт агенту пригодную для повторного использования личность разработчика.
Опасный путь часто бывает косвенным. Локальный агент открывает SSH-сессию к машине разработки. Из-за глобального правила эта сессия пересылает ваш агент. Агент для программирования запускает скрипт репозитория на машине разработки. Скрипт загружает зависимость или открывает второе SSH-соединение. Второе соединение может использовать сокет, который первая сессия разместила на машине.
Это опаснее, чем один git fetch, введённый человеком: агент может выполнить множество вызовов инструментов, не остановившись и не задавшись вопросом, зачем удалённой оболочке доступ к несвязанной системе. В репозитории агент также может встретить инструкции использовать определённый псевдоним хоста. За ним могут скрываться ProxyCommand, правило Match или унаследованное правило пересылки, незаметные оператору.
Считайте это четырьмя отдельными разрешениями:
- Разрешение агенту открыть удалённую оболочку.
- Разрешение этой удалённой оболочке обратиться к другому SSH-хосту.
- Разрешение второй системе принять личность разработчика.
- Разрешение агенту инициировать второе соединение.
Команды часто объединяют все четыре пункта в фразу «агенту нужен SSH». Она не описывает границу доступа. У удалённой оболочки и возможности повторно использовать подпись разные последствия, поэтому решения по ним должны приниматься отдельно.
Проверьте, как запускается процесс агента. Если он наследует SSH_AUTH_SOCK из интерактивного терминала, то уже может получить доступ к локальным идентичностям агента для прямой аутентификации по SSH. Если затем он подключится к хосту с включённой пересылкой, этот хост получит второй путь к тем же идентичностям. Обёртка, очищающая переменную окружения, снижает риск случайного локального использования, но не заменяет безопасную конфигурацию SSH или отдельную идентичность для автоматизации.
Проверьте также инструкции агента и оболочки автоматизации на наличие ssh -A, scp -A или псевдонима, который разворачивается в эти команды. scp и sftp используют настройки SSH-транспорта, поэтому привычка включать пересылку может распространиться дальше команды, с которой всё началось. Настройте пересылку там, где ей место: в описании соединения, с явным значением по умолчанию no.
Jump-хосту не нужен ваш агент
Многие разработчики включают пересылку, потому что им нужно пройти через bastion к внутренней системе. Это был привычный обходной путь, когда единственной удобной моделью считалось «войти на bastion, а затем снова выполнить SSH». Он по-прежнему популярен, потому что быстро работает при настройке. Но при этом bastion становится машиной, способной повторно использовать вашу личность.
Используйте ProxyJump, если первому хосту нужно только передавать трафик. Локальный клиент может аутентифицироваться на конечном хосте через туннель на jump-хосте, не пересылая ему сокет агента.
Host engineering-bastion
HostName bastion.example
User developer
ForwardAgent no
Host release-host
HostName release.internal.example
User deploy
ProxyJump engineering-bastion
ForwardAgent no
В такой конфигурации локальный SSH-клиент устанавливает конечное SSH-соединение через bastion. Bastion передаёт зашифрованный транспортный трафик. Он не получает удалённый SSH_AUTH_SOCK, через который можно было бы запрашивать подписи у вашего агента.
Проверяйте маршрут, а не исходите из предположения, что конфигурация работает правильно:
ssh -vvv release-host
В отладочном выводе найдите соединение через proxy jump и убедитесь, что пересылка агента не включается. Затем после входа на конечный хост проверьте удалённое окружение:
printf 'SSH_AUTH_SOCK=%s\n' "${SSH_AUTH_SOCK:-}"
ssh-add -l
Если вы намеренно отключили пересылку, переменная сокета должна быть пустой. Сообщение ssh-add -l об отсутствии соединения с агентом также соответствует отсутствию пересланного агента. При расследовании не проверяйте только конечный хост. Проверьте каждый интерактивный переход, где кто-то мог включить пересылку.
Бывают ситуации, когда jump-хосту действительно нужно самому аутентифицироваться на другом хосте, например во время контролируемого развёртывания. Тогда jump-хосту нужна собственная идентичность для развёртывания или короткоживущая идентичность рабочей нагрузки. Это не означает, что ему нужно одалживать все ключи, загруженные в агент на ноутбуке разработчика.
У удалённой автоматизации должна быть собственная идентичность
Удалённый сервер сборки, хост развёртывания или ИИ-инструмент должны входить от имени назначенной им рабочей нагрузки, а не разработчика, который случайно начал сессию. Это изменение архитектуры устраняет потребность в пересылке, а не просто делает её менее удобной.
Идентичность рабочей нагрузки должна давать доступ только к тем сервисам, которые ей необходимы. Для исходного репозитория используйте идентичность для развёртывания, ограниченную этим репозиторием, если сервис это поддерживает. Для SSH-хоста назначьте отдельный публичный ключ отдельной учётной записи и ограничьте команды или права этой учётной записи, если сервер это позволяет. В системе на основе SSH-сертификатов выдавайте короткоживущие сертификаты с principals, соответствующими роли рабочей нагрузки.
SSH-сертификаты могут сузить доступ, но не превращайте их в магическое решение. Principal сертификата определяет принимаемые учётные записи только при корректной проверке principals на сервере. Короткий срок действия ограничивает время, в течение которого подпись может пройти аутентификацию, но скомпрометированный процесс всё ещё может использовать сертификат в это окно. Проверьте конфигурацию сервера, доверяющую центру сертификации, и правила учётных записей, которые используют эти principals.
Не переиспользуйте личный публичный ключ разработчика как «временную» идентичность для CI-исполнителя или удалённого агента. Это запутывает аудит. Если в записи аутентификации указано, что вход выполнен личным ключом, трудно понять, действовал ли разработчик, задача сборки или скомпрометированная удалённая оболочка через пересылку.
Для действий агента по HTTP и SSH можно хранить учётные данные в локальном шлюзе действий и возвращать агенту только команду или результат API-запроса. Sallyport использует эту модель в macOS: его хранилище содержит SSH-ключ, а помощник sp-ssh выполняет действие SSH, не передавая сам ключ агенту.
Здесь важна настоящая операционная граница, а не формулировки. Агент запрашивает именованное действие, шлюз применяет учётные данные, а агент получает результат. Агент не получает сокет, который можно передать несвязанному удалённому процессу. Поэтому подтверждения и журналы отражают конкретное действие, а не безграничную возможность запрашивать будущие подписи.
Запросы подтверждения снижают риск, но не устраняют проблему доверия
Иногда короткая задача обслуживания действительно требует пересылки, а отдельная идентичность ещё не подготовлена. В таком случае пересылайте как можно меньше полномочий на подпись и делайте каждое оставшееся использование заметным. Это временная мера, а не постоянная архитектура.
Запустите отдельный сокет агента, а не пересылайте агент, в котором хранится ваш обычный набор идентичностей. Добавьте только ключ, необходимый для задачи обслуживания, задайте короткий срок действия и обязательное подтверждение:
ssh-agent -a "$HOME/.ssh/maintenance-agent.sock" > "$HOME/.ssh/maintenance-agent.env"
. "$HOME/.ssh/maintenance-agent.env"
ssh-add -c -t 900 ~/.ssh/maintenance_ed25519
ssh -o ForwardAgent=yes [email protected]
ssh-add -c запрашивает подтверждение перед подписью агента. -t 900 удаляет идентичность через 15 минут. Проверьте руководство ssh-add(1) для установленной версии OpenSSH, потому что поведение подтверждений зависит от локального агента и его пользовательского интерфейса.
Используйте для этой задачи отдельную оболочку. После завершения работы удалите идентичность и остановите агент:
ssh-add -D
ssh-agent -k
Эта последовательность не позволяет выполнять последующие запросы через временный сокет. Она не отзывает уже выданные подписи, не закрывает уже аутентифицированные соединения и не удаляет данные, которые удалённый процесс уже получил. Закройте удалённую сессию и проверьте системы, доступные через эту идентичность.
В версиях OpenSSH с соответствующей функцией есть ограничения назначения через ssh-add -h. Они могут ограничить хосты, для которых агент подписывает запросы, на основе ключей хостов из known_hosts. Для контролируемой среды это стоит изучить, но такие ограничения добавляют зависимости конфигурации, которые команды часто перестают поддерживать. Устаревший или неполный known_hosts может превратить защитную меру в сбой в самый неподходящий момент. Проверьте её с точным маршрутом через jump-хост и псевдонимами, которые использует автоматизация.
Не полагайтесь на усталость от запросов как на границу безопасности. Скомпрометированный хост может многократно просить подписи и указывать назначения, похожие на обычную инфраструктуру. Если во время инцидента оператор быстро подтверждает запросы, защита оказывается слабее, чем кажется. Узкий ключ и короткий срок действия уменьшают последствия даже при подтверждении.
Журналы должны различать сессии и действия SSH
Журналы SSH-аутентификации сообщают, что ключ прошёл проверку на сервере. Они редко показывают, зачем запрашивалась подпись и была ли пересылка агента частью маршрута. Если вы разрешаете автоматизированные удалённые действия, собирайте достаточно данных, чтобы восстановить, кто запустил агента, какой процесс получил подтверждение, к какому назначению он обратился и какое действие запросил.
Храните записи уровня сессии отдельно от записей уровня действия. Запись сессии отвечает на вопрос, какой процесс агента получил доступ и когда этот доступ закончился. Запись действия показывает, какая команда SSH или какой вызов API был выполнен в рамках доступа. Если смешать всё в один общий журнал событий, расследование замедлится: оператору придётся восстанавливать цепочку причин и последствий по разрозненным фрагментам.
После подозрительного события с пересылкой проверьте обычные записи аутентификации на SSH-сервере. Точное расположение зависит от операционной системы и конфигурации сервиса, но часто используются записи системного журнала и журнал аутентификации демона SSH. Сопоставьте отпечаток принятого публичного ключа, имя учётной записи, исходный адрес и время с историей сессий на первом удалённом хосте.
Не делайте уверенных выводов только по IP-адресу. Пересланное соединение может исходить от сервера сборки, bastion, шлюза трансляции сетевых адресов или частной оверлейной сети. Сервер может определить источник TCP-соединения, но не того, кто инициировал запрос подписи на ноутбуке разработчика.
Защищённые от подделки записи полезны только тогда, когда система создаёт их вне контроля агента. Процесс, способный редактировать собственную историю действий, может удалить подозрительный второй переход до начала проверки. Sallyport записывает сессии агентов и отдельные вызовы в один зашифрованный аудит-журнал с цепочкой хешей, а sp audit verify проверяет эту цепочку офлайн без ключа хранилища.
Какой бы инструмент вы ни выбрали, проверьте его записи на реальном неудачном отказе в авторизации и настоящей успешной команде SSH. Убедитесь, что журнал указывает запуск агента, назначение, ссылку на учётные данные или отпечаток, результат и время. Запись «инструмент завершён» не поможет ответить на вопрос об инциденте и повторном использовании личности.
Считайте раскрытие полномочий злоупотреблением авторизацией, а не автоматической кражей ключа
Если вы обнаружили, что переслали агента на недоверенный хост, действуйте так, будто этот хост мог использовать вашу личность, пока сессия оставалась активной. Не ждите доказательств извлечения закрытого ключа. Реальный риск заключается в несанкционированной аутентификации, а закрытый ключ мог никогда не покидать ваш компьютер.
Сначала остановите дальнейшее использование. Закройте все SSH-сессии к этому хосту, удалите раскрытую идентичность из агента и отключите правило пересылки для хоста. Если идентичность была загружена в общий агент, не запускайте бездумно ssh-add -D посреди рабочего дня. Это может прервать законные сессии, но оставить затронутый публичный ключ разрешённым на серверах.
Затем удалите или отзовите разрешения в системах, доступных через эту идентичность. В обычной конфигурации с authorized_keys удалите публичный ключ из учётных записей, где он больше не должен работать, и при необходимости замените его новым. Для SSH-сертификатов отзовите сертификат по процедуре вашего центра сертификации и сервера либо дождитесь окончания короткого срока действия, если можете подтвердить приемлемость окна раскрытия. Для доступа к репозиториям отзовите или замените соответствующие учётные данные для развёртывания или пользователя через штатные настройки сервиса.
Исследуйте ограниченный период: от момента, когда пересылка стала доступна, до завершения удалённой сессии или прекращения обработки запросов локальным агентом. Проверьте записи аутентификации на конечных системах, историю удалённой оболочки, если ей можно доверять, записи задач и журналы действий. Если вы подозреваете взлом, сохраните журналы до очистки удалённого хоста.
Наконец, исправьте путь, который сделал это возможным. Если событие вызвало глобальное ForwardAgent yes, изменение записи только для скомпрометированного хоста оставит открытым следующий неизвестный хост. Если ИИ-сценарий унаследовал личный сокет, задайте ему явную конфигурацию соединения и отдельную идентичность рабочей нагрузки. Исправление должно по умолчанию делать опасный путь невозможным, а не просто напоминать людям о необходимости помнить нужный флаг.
Безопасная настройка по умолчанию должна работать даже в спешке
Пересылка сохраняется, потому что в нужный момент снимает препятствия. Разработчику нужен ещё один переход, сборке нужно загрузить закрытые данные или агенту нужно завершить задачу до срока. Это реальные потребности. Но они не оправдывают размещение широко доверенной личности разработчика на каждой машине, оказавшейся на маршруте.
Глобально задайте ForwardAgent no. Используйте ProxyJump для bastion, которым нужен только транспорт. Выдайте удалённой автоматизации идентичность, описывающую разрешённые действия. Если временного исключения не избежать, изолируйте одну идентичность, включите подтверждение, задайте короткий срок действия и удалите ключ после завершения задачи.
Выполните ssh -G для псевдонимов хостов, которыми ваша команда пользуется на этой неделе. Эта небольшая проверка обнаружит незаметную ошибку конфигурации до того, как удалённый процесс получит сервис подписи, которого у него не должно быть.
Вопросы и ответы
Может ли удалённый сервер украсть мой закрытый ключ через пересылку SSH-агента?
Обычно нет. Удалённая машина не может прочитать закрытый ключ через обычный пересланный сокет агента, но пока соединение доступно, она может попросить локальный агент подписать запросы на аутентификацию. Этого достаточно, чтобы войти в системы, принимающие данный ключ.
Включает ли ssh -A пересылку агента?
Нет. ssh -A явно включает пересылку, а ssh -a отключает её для текущего соединения. Файл конфигурации всё равно может привести к неожиданному результату, поэтому проверьте фактическое значение командой ssh -G host | grep '^forwardagent '.
Нужна ли пересылка агента при использовании ProxyJump?
ProxyJump создаёт маршрут через промежуточный узел, но не требует, чтобы агент был доступен этому узлу. Настройте аутентификацию на конечном хосте локально и оставьте ForwardAgent no. Считайте jump-хост маршрутом, а не рабочей машиной, которой нужно передать вашу личность.
Защищает ли IdentitiesOnly от пересылки SSH-агента?
IdentitiesOnly yes определяет, какие ключи SSH-клиент предлагает при аутентификации. Эта настройка не мешает клиенту переслать сокет агента после входа. Отдельно задайте ForwardAgent no.
Достаточно ли ssh-add -c для защиты пересланного агента?
Ограничение с подтверждением снижает риск незаметного повторного использования ключа, поскольку локальный агент спрашивает разрешение перед каждой подписью. Но недоверенный хост от этого не становится безопасным: он может создавать повторяющиеся запросы, а поспешное подтверждение всё равно разрешает настоящую попытку входа. Используйте такую настройку как временный барьер, а не как обычную архитектуру.
Как безопаснее переслать SSH-агента для временной задачи?
Используйте отдельный агент только с узко ограниченным ключом, задайте короткий срок действия и обязательное подтверждение, а пересылайте его лишь на конкретный хост для короткой задачи. После завершения удалите ключ. Личный агент с несколькими долгоживущими ключами для этого не подходит.
Может ли ИИ-агент для программирования злоупотребить моей пересланной SSH-личностью?
Риск зависит от того, что унаследовал агент и какие команды может выполнять удалённая учётная запись. Агент, способный запускать команды оболочки, может вызвать ssh, получить SSH_AUTH_SOCK из окружения или открыть удалённую сессию с пересланным сокетом. Дайте агенту специально подготовленный путь к удалённым ресурсам, а не личную учётную запись разработчика.
Что делать, если я переслал SSH-агента на недоверенный хост?
Сначала удалите открытый публичный ключ из систем, где он даёт доступ, или отзовите сертификат, если вы используете SSH-сертификаты. Затем проверьте журналы аутентификации в этих системах и замените ключ, если не можете точно ограничить период его использования. Остановка локального агента прекращает будущие запросы, но не отменяет уже созданные подписи.
Безопасна ли пересылка SSH-агента в CI?
Пересылка может работать на CI-исполнителе, но даёт его процессам возможность запрашивать подписи у агента. Лучше использовать учётные данные для развёртывания, принадлежащие самому исполнителю, короткоживущий сертификат или шлюз действий, который сам выполняет операцию SSH. Исполнитель не должен одалживать ежедневную личность разработчика.
Как проверить, активна ли пересылка SSH-агента?
На клиенте выполните ssh -G target | grep '^forwardagent ', чтобы до подключения увидеть фактическую настройку. На удалённом хосте непустая переменная SSH_AUTH_SOCK и рабочий вывод ssh-add -l показывают, что в этой сессии доступен агент. При расследовании подозрительного маршрута проверьте обе стороны.