阅读需 8 分钟

代理环境变量:审计智能体 API 流量

在 AI 智能体调用内部 API 前审计代理环境变量。验证变量继承,测试 NO_PROXY 匹配,并控制不安全的代理路由。

代理环境变量:审计智能体 API 流量

代理环境变量是可执行的路由指令,不是什么无害的 shell 偏好设置。如果 AI 编程智能体继承了 HTTP_PROXYHTTPS_PROXYALL_PROXYNO_PROXY,一个看似直接发往内部 API 的请求,可能在智能体开展任何有效工作之前,就沿着另一条网络路径发出。

我见过一些团队花了好几天审查令牌权限范围和端点允许列表,最后却发现智能体运行器继承了开发者 shell 中用于本地调试的代理。凭据有效,API 客户端也完全按照配置运行,但流量还是去了没人预期的地方。在授予智能体访问内部服务的权限之前,先审计进程环境。

代理变量改变的是路由,不只是连接设置

代理变量会告诉支持它的客户端,把请求交给中间代理。对于普通 HTTP,客户端通常会把完整的目标 URL 发给代理。对于 HTTPS,客户端通常会请求代理为目标主机打开一个 CONNECT 隧道,然后通过这条隧道执行 TLS。

这个区别很重要,因为即使代理无法解密 HTTPS,它仍会影响可用性、目标控制、DNS 行为和可观测性。代理可以拒绝连接,在 TCP 层重定向连接,记录请求的主机和端口,或者成为智能体访问某项服务的唯一通道。

应将这些变量视为智能体对外发起连接权限的一部分:

  • HTTP_PROXYhttp_proxy 通常影响 http:// URL。
  • HTTPS_PROXYhttps_proxy 通常影响 https:// URL。
  • 支持它们的客户端通常会把 ALL_PROXYall_proxy 作为后备设置。
  • NO_PROXYno_proxy 通常会让指定目标绕过代理。

“通常”这个词很关键。环境变量是一种约定,不是每个运行时都以完全相同方式实现的网络标准。智能体可能调用命令行客户端、使用某种语言的 HTTP 库、运行软件包管理器,或启动辅助进程。每一层都可能独立决定是否使用代理。

变量未设置,也不能证明请求一定会直连。客户端可能读取配置文件、使用系统代理设置、遵循 PAC 文件,或者显式调用本地中继。本文聚焦环境变量,是因为它们很容易被继承,在拥挤的进程环境中很难发现,而且常常被当作临时设置,直到它们变成永久配置。

启动器决定智能体继承什么

智能体只能接收到自身进程环境中已有的变量,或由父进程向下传递的变量。你检查 env 的终端,可能与实际运行智能体的进程毫无关系。

在 macOS 上尤其容易出错。从交互式 shell 启动的进程会继承 shell 中导出的变量。由 Finder 启动的图形应用,或通过 launchd 启动的服务,则遵循不同的继承路径。IDE 可能为集成终端启动一种环境,为扩展宿主启动另一种环境。昨天启动的后台智能体,也可能在 shell 变量消失很久后继续保留旧的代理值。

在修改设置前,先绘制执行链。明确回答四个问题:

  1. 哪个进程启动了智能体?
  2. 智能体是否会启动 shell、软件包工具、测试运行器或远程辅助进程?
  3. 其中哪些进程会发起 HTTP 请求?
  4. 每个环境变量由哪个启动点提供?

除非你能指出父进程,并从该终端复现运行过程,否则不要接受“智能体在我的终端里运行”这种说法。智能体可能是编辑器、任务运行器或使用已保存环境的自动化服务的子进程。

父进程注入的代理变量会传递给所有子进程,除非子进程主动移除它。这就是为什么一行 shell 导出命令会悄悄改变软件包安装、源代码管理辅助工具、云 CLI、浏览器自动化和测试装置发出的请求。受影响的请求可能根本不是你启动智能体时想到的那一个。

不同客户端对代理变量的解释不同

HTTP_PROXYHTTPS_PROXYNO_PROXY 没有统一的解释方式。任何假定某个客户端的行为适用于另一个客户端的安全审查,都不完整。

curl 文档中有一个很多人会忽略的重要例外:curl 只接受小写的 http_proxy 来配置 HTTP 代理。文档解释说,这样可以避免 CGI 问题,因为传入的 Proxy: 请求头可能会变成 HTTP_PROXY 环境变量。curl 会接受其他几个代理变量的大写形式,但 HTTP 这一例外是有意设计的。

Go 将 http.ProxyFromEnvironment 说明为一个读取 HTTP_PROXYHTTPS_PROXYNO_PROXY 以及其小写替代形式的函数。它的行为也包含 CGI 防护:当 CGI 环境中存在 REQUEST_METHOD 时,Go 不会使用 HTTP_PROXY,因为它可能来自请求头。这个保护并不意味着 Go 进程天然安全。仍需审查 HTTPS_PROXY、小写形式、显式传输设置以及非 CGI 环境。

许多 JavaScript 应用会让情况更复杂。Node.js 运行时内置的 HTTP API 历来没有统一采用一种环境代理策略。应用及其依赖通常会自行添加代理支持。同一次智能体运行中,一个命令可能遵守 HTTPS_PROXY,另一个命令可能忽略它,第三个命令则可能读取自定义选项。

不要用一张记录各种运行时传闻的表格来代替验证。找出每个能够联网的可执行文件并进行测试。记录可执行文件版本、调用方式、目标 URL、相关变量,以及它是直接连接还是通过预期代理连接。将结果放入智能体部署记录中,因为依赖更新可能改变行为。

一个经常被混淆的区别是:了解代理不等于强制使用代理。支持代理变量的客户端在变量存在时可以被引导到代理;但当变量缺失、格式错误、被 NO_PROXY 绕过,或辅助进程忽略变量时,它仍可能直接连接。如果你需要阻止直接外连,应在网络边界执行这一策略,而不是寄希望于每个库都会读取环境变量。

对内部名称明确测试 NO_PROXY

NO_PROXY 是一个绕过列表,错误的绕过列表可能让内部流量经过代理,也可能让你原本希望接受检查的流量直接发出。不能假定其中任何一种结果是安全的。

大多数实现接受以逗号分隔的条目。除此之外,各实现就会出现差异。客户端可能以不同方式处理 registry.corp.example.corp.examplecorp.example10.20.0.0/1610.20.30.40localhost*。某个客户端中有效的后缀匹配,在另一个客户端中可能范围过大,或者完全不起作用。带端口的条目也没有统一行为。

在测试证明其含义之前,避免使用范围过大的条目。裸域名后缀可能会让你没有打算绕过的主机也被豁免。星号可能比审查者预期的范围更广泛地关闭代理。CIDR 支持在存在时很有用,但不能假定它具有可移植性。明确的内部主机名看起来普通,但在这里,普通反而有用。

先列出一小组必须直连的命名服务,例如内部源代码主机、制品仓库和服务发现端点。只有在确认客户端会按预期匹配后缀,并且后缀下的每台主机都应该使用相同路由时,才添加域名后缀。

还要区分主机名匹配和名称解析。客户端通常会先决定是否适用 NO_PROXY,然后才建立连接。如果列表中包含主机名,即使该主机解析到预期范围之外的地址,匹配也可能成功。如果列表中包含 IP 网段,客户端可能需要先解析名称才能做出判断。具体顺序由实现决定。

绕过测试需要使用你能控制、并能在日志中识别的目标。不要使用生产 API,再根据 200 响应推断结果。内部测试端点应该报告远端地址,或在访问日志中输出请求标识符。然后比较存在绕过条目和不存在绕过条目时发出的请求。你需要的是连接路径的证据,而不是根据应用输出做出的猜测。

HTTPS 会隐藏内容,但代理仍能获得有意义的信息

避免在环境中放入凭据
让智能体的 API 操作经过一个签名的 macOS 应用,而不是把凭据交给每个辅助进程。

HTTPS CONNECT 隧道通常会保护请求头和请求正文,使其不被传统正向代理看到。但这并不意味着代理无关紧要。代理能看到 CONNECT 请求中指定的主机和端口、连接时间、字节数,以及通常的源地址。根据客户端和网络情况,相关 DNS 流量可能还会泄露更多信息。

如果客户端信任代理用于 TLS 拦截的证书颁发机构,代理就能读取解密后的 API 凭据。这种情况会出现在一些受管理的企业网络和调试环境中。是否信任本地根证书是安全边界决策,不是便利设置。如果智能体进程信任该根证书,代理操作者就能检查拦截策略适用的任何主机上的流量。

明文 HTTP 更糟。正向代理可以收到完整 URL 和请求头,其中可能包括 bearer token 或基本身份验证信息。不要因为“网络是私有的”,就允许智能体使用明文 HTTP 访问经过身份验证的内部 API。代理环境变量只是让私有网络假设失效的多种方式之一。

另一个不太明显的问题是代理 URL 中嵌入了凭据,例如 http://user:[email protected]:8080。这个值可能出现在诊断输出、崩溃报告、 shell 历史记录、进程检查结果或记录环境变量的日志中。代理身份验证应使用适合所在环境的受管机制,安全审查也应将代理凭据本身视为机密。

如果你无法确认代理操作者、代理监听地址,以及是否可能进行 TLS 拦截,就不要让特权智能体流量经过它。这不是多疑。代理已经成为自动化进程与敏感服务之间路径的一部分。

在不倾倒机密的情况下审计运行中的进程

先清点变量名称和代理端点,同时避免完整导出环境。在可能启动智能体的 shell 中运行:

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

输出应类似:

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

如果代理 URL 中包含用户信息,不要把原始输出粘贴到工单中。删除凭据后,记录协议、主机和端口。回环地址并不自动代表安全。本地代理经常属于合法调试工具,但恶意软件和不需要的软件也可能监听回环地址。确认哪个进程拥有该监听端口。

在 macOS 上,可以这样检查监听端口:

lsof -nP -iTCP:8888 -sTCP:LISTEN

正常结果会显示命令和进程 ID。如果没有预期进程拥有该端口,就先停下来调查。不要因为地址以 127.0.0.1 开头,就让智能体把凭据发送给这个监听程序。

接下来检查实际的智能体进程。ps 可以在 macOS 上显示进程环境,但也可能暴露无关的机密。将访问限制在机器所有者或管理员,收集必要的信息,不要把结果粘贴到聊天或共享日志中。

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

AGENT_PID 设置为运行中智能体的进程 ID。如果代理凭据存在,这条命令仍可能打印出来,因此应在可信终端上运行,并在保存证据前完成脱敏。如果结果与 shell 清单不同,说明父进程注入或移除了变量。

对于由 launchd 启动的进程,应检查任务定义和启动器,而不是依赖终端。launchctl getenv HTTPS_PROXY 可以显示当前 launchd 域中的变量,但那里没有变量,并不能证明某个特定任务没有代理。任务可以定义自己的环境,包装脚本也可以在启动智能体前导出变量。

用无害请求证明路由

锁定时停止操作
保管库锁定后,所有操作都会被拒绝,直到你通过硬件控制解锁 Sallyport。

配置审查告诉你应该发生什么,受控请求告诉你实际发生了什么。两者都需要。

创建或使用一个不会包含敏感信息的端点,让它记录对端地址和请求路径。然后用详细连接输出发起请求。curl 很有用,因为它会显示自己是否连接到代理,以及是否发送了 CONNECT 请求。

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

curl 通过代理处理 HTTPS 时,详细输出通常会包含类似这样的行:

* 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

然后有意进行直连对比:

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 会清除继承的变量,因此必须恢复程序运行所需的基本设置:

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 代理套接字或软件包缓存等变量的工具无法运行。这种失败能提供有用信息。逐个恢复智能体需要的变量,并记录每个变量存在的原因。智能体启动器应足够精简,让另一位工程师读完后就能理解请求会发往哪里。

网络控制应为此提供支持。如果智能体只能访问少数内部服务,请根据所在环境使用防火墙规则、执行策略的出口代理或专用网络分段。环境变量只能为遵守它的客户端选择路由,不能阻止已被入侵的进程或不遵守设置的库直接打开套接字。

不要把凭据交给选择路由的进程

撤销受污染的运行
如果智能体继承的环境已经不再可信,可从 Sessions 日志中撤销该运行。

保持代理配置整洁,可以减少意外改道,但这并不意味着把 API 令牌交给自主智能体是明智的。如果智能体环境中包含 bearer token,那么所有能够访问该环境的进程都进入了这个机密的暴露路径。

Sallyport 采用了不同的边界:智能体通过 MCP shim 请求 HTTP 或 SSH 操作,凭据留在应用的加密保管库中,由应用执行操作。这样可以限制智能体进程中代理变量泄露所造成的影响,因为智能体从未以明文获得 API 或 SSH 凭据。

不要夸大这个边界的作用。凭据网关不会自动为智能体运行的每条命令定义安全代理行为,也不能让有害目标变得无害。你仍需控制允许访问的目标和操作,审查实际执行网络调用的应用进程,并保留请求路径的证据。

真正有用的区分是机密保管与网络路由。机密保管回答谁能读取凭据,网络路由回答哪个中间方处理请求。团队经常解决了其中一个问题,就以为两个问题都解决了。事实并非如此。

将无法解释的代理使用视为需要控制的事件

如果特权智能体通过未知代理发出了请求,应在同一受污染环境中继续调查之前先停止运行。记录智能体进程 ID、父进程、代理地址、受影响的目标名称和时间范围。按照事件处理流程保存相关智能体和代理日志。

然后从源头移除变量。在当前终端中删除变量,只能修复该终端未来启动的子进程。检查 shell 配置文件、IDE 任务设置、启动代理、CI 配置、包装脚本,以及任何会写入环境设置的配置管理系统。修正启动点后,重启受影响的进程。

按协议评估凭据。如果受影响的流量是带身份验证的明文 HTTP,请轮换暴露的凭据。如果是通过代理的 HTTPS,请确认端点是否执行了正常 TLS 验证,以及智能体是否信任拦截证书。不要因为 HTTPS 就假定无需审查,也不要在保留同一注入路径的情况下盲目轮换凭据。

最后,在智能体启动位置加入预检。出现意外代理变量时,应终止运行或要求明确审查。检查应报告变量名称和经过脱敏的端点,将其与该任务批准的路由进行比较,并留下预检已经运行的记录。第一个无法解释的代理是警告,第二个则是你选择继续保留的部署缺陷。

常见问题

HTTP_PROXY 和 HTTPS_PROXY 有什么作用?

它们会告诉兼容的 HTTP 客户端通过代理发送请求,而不是直接连接目标地址。这些变量并不能强制每个程序都遵守,因此必须测试实际使用的智能体、工具及其子进程。

HTTPS 代理能读取 API 令牌吗?

有时可以。处理 HTTPS CONNECT 隧道的代理通常能看到目标主机名和端口,但看不到加密的请求正文。只有在客户端信任某个允许代理拦截 TLS 的证书颁发机构,或者客户端在 TLS 开始前发送敏感信息时,代理才能读取 HTTPS 流量。

NO_PROXY 用来做什么?

NO_PROXY 是一个绕过列表。许多客户端会根据它直接连接列出的主机、域名或 IP 地址,但具体匹配规则因客户端库和版本而异。

NO_PROXY 支持通配域名吗?

不要假定前导点、裸后缀、CIDR 网段或星号在所有程序中的含义都一样。使用智能体实际运行的确切命令或库进行直连测试,然后让列表保持简短且明确。

为什么代理环境变量会给 AI 智能体带来风险?

如果智能体进程从 shell、IDE、CI 运行器或服务管理器继承了代理地址,请求可能会未经提示就通过该路径发出。代理属于 VPN 辅助工具、调试工具、酒店网络配置或未知本地监听程序时,风险会更高。

如何在 macOS 上审计代理设置?

先进行经过脱敏的清点:env | grep -Ei '(^|_)(http|https|all|no)_proxy='。然后检查启动器和进程环境,确认代理的所有者及监听地址,并在允许访问内部服务前,用无害请求验证实际路由。

所有 HTTP 客户端都会遵守代理环境变量吗?

不是。curl、Go、Python、Java、Node 软件包和命令行工具会分别决定如何处理环境变量。有些支持大写和小写名称,有些带有 CGI 防护,有些则需要单独配置代理。

NO_PROXY 能防止 DNS 泄露吗?

直连仍可能通过 DNS 泄露主机名,例如解析器位于你预期网络边界之外时。直连也可能因为目标没有可用的直接路由而失败,所以测试必须同时检查 TCP 路径和应用响应。

如果智能体使用了未知代理,我该怎么办?

如果敏感流量的路由发生变化,或未知代理收到了请求,应将其视为安全事件。停止智能体,在实际启动点移除继承的变量,撤销任何可能经过不可信 HTTP 路径的凭据,并保留进程和代理日志。

凭据网关能消除代理配置风险吗?

不能。网关可以让 API 和 SSH 凭据留在智能体进程之外,从而减少凭据暴露,但它不会自动决定其他客户端如何解析代理变量。你仍需要干净的启动环境和经过测试的网络路径。

Sallyport

Sallyport 替你的 AI 智能体执行 API 调用和 SSH 命令。密钥留在你 Mac 上的本地密钥库里;每次运行由你批准,每个操作都落入一份密封的审计日志。

© 2026 Sallyport · 依据 Apache-2.0 开源 · Oleg Sotnikov