# Переменные окружения прокси: проверьте трафик API-агента

Переменные окружения прокси это исполняемые инструкции по маршрутизации, а не безобидные настройки shell. Если AI-агент для программирования получает `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY` или `NO_PROXY`, запрос, который выглядел как прямой вызов внутреннего API, может пройти по другому сетевому маршруту еще до того, как агент успеет сделать что-либо полезное.

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

## Переменные прокси меняют маршрут, а не только настройки подключения

Переменная прокси сообщает совместимому клиенту, что запрос нужно передать посреднику. Для обычного HTTP клиент обычно отправляет посреднику полный URL назначения. Для HTTPS клиент обычно просит прокси открыть туннель `CONNECT` к целевому узлу, а затем устанавливает TLS внутри этого туннеля.

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

Считайте эти переменные частью полномочий агента на исходящие подключения:

- `HTTP_PROXY` и `http_proxy` обычно влияют на URL `http://`.
- `HTTPS_PROXY` и `https_proxy` обычно влияют на URL `https://`.
- `ALL_PROXY` и `all_proxy` служат запасным вариантом в клиентах, которые их поддерживают.
- `NO_PROXY` и `no_proxy` обычно исключают адреса назначения из проксирования.

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

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

## Средство запуска определяет, что унаследует агент

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

В macOS особенно легко ошибиться. Процесс, запущенный из интерактивного shell, получает экспортированные переменные shell. Графическое приложение, запущенное через Finder, или служба, запущенная через `launchd`, используют другой путь наследования. IDE может запускать встроенный терминал с одним окружением, а хост расширений с другим. Фоновый агент, запущенный вчера, может хранить старое значение прокси еще долго после исчезновения переменной из shell.

Перед изменением настроек составьте карту цепочки запуска. Ответьте на четыре конкретных вопроса:

1. Какой процесс запускает агента?
2. Запускает ли агент shell, инструменты работы с пакетами, тестовые среды или удаленные помощники?
3. Какие из этих процессов выполняют HTTP-запросы?
4. Какая точка запуска передает каждую переменную окружения?

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

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

## Семантика переменных прокси различается у клиентов

Единой трактовки `HTTP_PROXY`, `HTTPS_PROXY` и `NO_PROXY` не существует. Проверка безопасности, в которой поведение одного клиента автоматически приписывают другому, неполна.

В документации curl описано важное исключение, которое многие упускают: curl принимает только `http_proxy` в нижнем регистре для настройки HTTP-прокси. В документации объясняется, что это защищает от проблемы CGI, когда входящий заголовок `Proxy:` может превратиться в переменную окружения `HTTP_PROXY`. Для нескольких других переменных прокси curl принимает варианты в верхнем регистре, но это исключение для HTTP сделано намеренно.

Go описывает `http.ProxyFromEnvironment` как функцию, читающую `HTTP_PROXY`, `HTTPS_PROXY` и `NO_PROXY`, а также варианты в нижнем регистре. В ней тоже есть защита CGI: если в окружении CGI присутствует `REQUEST_METHOD`, Go отказывается использовать `HTTP_PROXY`, потому что его мог передать заголовок запроса. Это не значит, что процесс Go безопасен. Нужно по-прежнему проверить `HTTPS_PROXY`, варианты в нижнем регистре, явные настройки транспорта и контексты, не связанные с CGI.

Многие приложения на JavaScript еще сильнее усложняют картину. Встроенные HTTP API среды Node.js исторически не задавали единой политики работы с прокси через окружение. Приложения и их зависимости часто добавляют поддержку прокси самостоятельно. Одна команда может учитывать `HTTPS_PROXY`, другая в том же запуске агента может ее игнорировать, а третья читать отдельный пользовательский параметр.

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

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

## Для внутренних имен нужны явные тесты NO_PROXY

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

Большинство реализаций принимает записи, разделенные запятыми. Дальше начинаются различия. Клиенты могут по-разному обрабатывать `registry.corp.example`, `.corp.example`, `corp.example`, `10.20.0.0/16`, `10.20.30.40`, `localhost` и `*`. Сопоставление суффикса, работающее в одном клиенте, может оказаться слишком широким или полностью не работать в другом. Записи с указанным портом также ведут себя неодинаково.

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

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

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

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

## HTTPS скрывает содержимое, но прокси все равно получает важные данные

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

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

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

Менее очевидная проблема связана с URL прокси, содержащим учетные данные, например `http://user:password@proxy.example:8080`. Такое значение может попасть в диагностический вывод, отчеты о сбоях, историю shell, список процессов или журналы, где записывается окружение. Для аутентификации прокси используйте управляемый механизм, подходящий вашей среде, а учетные данные прокси при проверке безопасности считайте самостоятельными секретами.

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

## Проверьте работающий процесс, не раскрывая его секреты

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

```sh
env | grep -Ei '(^|_)(http|https|all|no)_proxy='
```

Вывод должен выглядеть примерно так:

```text
HTTPS_PROXY=http://127.0.0.1:8888
NO_PROXY=localhost,127.0.0.1,registry.corp.example
```

Если URL прокси содержит сведения о пользователе, не вставляйте исходный вывод в заявку. Запишите схему, узел и порт после удаления учетных данных. Loopback-адрес не гарантирует безопасность. Локальные прокси часто принадлежат легитимным инструментам отладки, но на loopback также могут прослушивать порт вредоносные или нежелательные программы. Проверьте, какой процесс владеет портом.

В macOS просмотреть слушающий процесс можно так:

```sh
lsof -nP -iTCP:8888 -sTCP:LISTEN
```

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

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

```sh
ps eww -p "$AGENT_PID" | tr ' ' '\n' | grep -Ei '(^|_)(http|https|all|no)_proxy='
```

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

Для процессов, запущенных через `launchd`, проверяйте описание задания и средство запуска, а не полагайтесь на терминал. Команда `launchctl getenv HTTPS_PROXY` может показать переменную в текущем домене launchd, но ее отсутствие не снимает подозрений с конкретного задания. Задание может задавать собственное окружение, а скрипт-обертка экспортировать переменные непосредственно перед запуском агента.

## Подтвердите маршрут безопасным запросом

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

Создайте или используйте нечувствительную точку, которая записывает адрес узла-партнера и путь запроса. Затем выполните запрос с подробным выводом соединения. curl удобен тем, что показывает подключение через прокси и отправку запроса `CONNECT`.

```sh
HTTPS_PROXY=http://127.0.0.1:8888 \\
NO_PROXY= \\
curl -v --connect-timeout 5 https://probe.corp.example/agent-proxy-check
```

Когда curl использует прокси для HTTPS, подробный вывод обычно содержит строки такого вида:

```text
* Uses proxy env variable HTTPS_PROXY == 'http://127.0.0.1:8888'
* Establish HTTP proxy tunnel to probe.corp.example:443
> CONNECT probe.corp.example:443 HTTP/1.1
```

Затем намеренно выполните прямое сравнение:

```sh
HTTPS_PROXY=http://127.0.0.1:8888 \\
NO_PROXY=probe.corp.example \\
curl -v --connect-timeout 5 https://probe.corp.example/agent-proxy-check
```

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

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

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

## Чистое окружение запуска лучше постоянных экспортов в shell

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

Используйте явное средство запуска агента. Начинайте с известного окружения, передавайте только нужные переменные и сделайте использование прокси видимым в команде запуска или обертке. В Unix-shell команда `env -i` очищает унаследованные переменные, поэтому нужно вернуть базовые параметры, необходимые программе:

```sh
env -i \\
PATH="$PATH" \\
HOME="$HOME" \\
LANG="${LANG:-en_US.UTF-8}" \\
NO_PROXY="localhost,127.0.0.1,registry.corp.example" \\
agent-command
```

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

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

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

## Не храните учетные данные в процессе, который выбирает маршрут

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

Sallyport использует другую границу: агент запрашивает HTTP- или SSH-действие через MCP shim, а учетные данные остаются в зашифрованном хранилище приложения, которое и выполняет действие. Это ограничивает последствия утечки переменной прокси в процессе агента, потому что агент никогда не получает API- или SSH-учетные данные в открытом виде.

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

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

## Рассматривайте необъяснимое использование прокси как инцидент, который нужно локализовать

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

Затем удалите переменную в источнике. Удаление в текущем терминале исправит только будущие дочерние процессы этого терминала. Проверьте профили shell, настройки задач IDE, launch agents, конфигурацию CI, скрипты-обертки и все средства управления конфигурацией, записывающие параметры окружения. После исправления точки запуска перезапустите затронутый процесс.

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

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