你的 API 客户端就是凭证保险箱:审计它
API 客户端的安全状况比你想象的更重要。学习如何审计桌面开发工具中的静默插件安装、遥测和数据外传。

一位开发者在 Mac 上打开了一个 HTTP 客户端,仅此而已。没有点击,没有同意弹窗,也没有“启用 AI 功能”的复选框。第二天,他的编码代理就无法通过身份验证连接到一个他从未配置过的远程 MCP 服务器,API 工作被路由到他从未安装过的 skills 上,还带着一个他从未批准、却被加密信任的启动钩子。这不是假设,而是一份近期记录详尽的 bug 报告所显示的内容:Postman 桌面应用就是这么干的,它自动把插件装进 Codex 和 Claude Code,改写全局代理配置,还试图把自己的会话钩子标记为受信任。如果你关心 API 客户端安全,这起事件应该会改变你对电脑上每一个桌面开发工具的看法。
Postman 桌面应用究竟做了什么
抛开愤怒,看看取证细节吧,因为这位报告者做了我们大多数人都不会做的功夫。Postman 自己的日志文件记录了一个 auto-existing-upgrade 事件:在一个已存在且已登录的桌面安装上,某个脚本运行并报告 claude-code:installed,codex:installed。没有任何提示。用户的全局 Codex 配置多了一个指向 Postman git 仓库的新 marketplace、一个已启用的插件,以及一条钩子信任记录。Claude Code 的设置里也多了一个已启用的插件,以及时间戳落在同一时段的安装记录。~/.local/bin 里出现了一个 Postman CLI 二进制文件。另外,一个远程 MCP 连接被指向了 Postman 的服务器,而这个连接随后认证失败、疯狂刷错误,这才是用户察觉到任何异常的唯一原因。
有两个细节值得反复读。第一,安装的会话启动钩子会告诉代理:在处理任何非平凡的 API 工程任务之前(设计、测试、mock、写文档、部署),都应该先加载 Postman 的 skill。这算不上按需集成,而是一个应用在改写另一家厂商应用的默认行为。第二,安装程序的源码里明确包含了获取该钩子哈希并将其写入代理受信任钩子状态的代码。信任机制的存在,本来就是为了让用户来批准钩子。让安装程序替自己批准钩子,相当于自己给自己签发许可条。
报告者很严谨,他们明确指出,仅凭信任记录并不能证明是谁创建的,也没有做任何数据外泄的断言。这份克制反而让报告更可信。它证明的事情已经足够:悄悄修改无关工具、往代理的会话启动中注入一条范围很广的行为指令,以及试图绕过用户同意控制。以我做过的任何安全评审来看,这种组合足以让一款产品在调查期间被移出设备清单。
为什么你的 API 客户端就是凭证保险箱
这一点团队往往严重低估。API 客户端并不只是带发送按钮的文本编辑器,它存放着你的 bearer token、会话 cookie、mTLS 客户端证书、OAuth 客户端密钥和签名请求头。数据库连接串和第三方密钥等环境变量也常常被粘贴进去。如果你使用 {{api_secret}} 这类模板语法,解析后的值就会在每次请求中流经应用,而且取决于厂商的遥测设计,可能也会流经他们的日志管道。一位调查 HIPAA 合规问题的从业者报告说,他确实发现了这种情况:解析后的密钥值出现在外发的日志流量中,而当该端点被拦截时,应用的功能也随之退化。我无法独立核实每一个细节,但这与我在其他“贴心”桌面工具中见过的架构模式吻合:把全量请求日志作为功能发货。
再加上代理的视角。当一个工具往你的编码代理里安装插件并注册会话钩子时,它就把自己的指令塞进了一个能读取代码库、执行 shell 命令、并持有自己一套凭证的进程上下文里。爆炸半径不再是“我的 API 集合泄露了”,而是“代理环境里可触及的每一个密钥,都只差一次糟糕的提示词或一个被攻陷的 skill,就会被发到错误的端点”。我们之前写过 安装后台守护进程的 CLI 工具 如何悄悄扩大自身的攻击面,这里是同一种模式,只不过载荷更聪明。相比住在另一家厂商信任存储里的指令钩子,一个能在 ps 里看到的守护进程简直算得上温和了。
如果诚实地对待威胁模型,结论会让人不舒服:桌面 API 客户端正处在密钥、网络访问,以及越来越多的代理控制平面的交汇处。它理应获得你给密码管理器或 CI 运行器的同等审视。但几乎没有人这么做。这正是这次事件所利用的缺口,也是为什么应对措施必须是流程化的,而不能只靠情绪。
为什么厂商的退出选项和设置开关靠不住
这类事件之后的第一反应,通常是去应用的偏好设置里翻找遥测开关,关掉,然后翻篇。我见过整个团队这么做,还把它当成了修复。这是表演,原因有三。
- 同意可能受版本或功能开关控制。Postman 的安装是由已登录现有账号上的功能开关触发的。今天存在的开关,并不能告诉你明天会上线哪些开关,而自动更新意味着明天不会先问你就来了。
- 开关管的是文档中写明的,而不是技术上可能的。隐私设置只控制厂商选择暴露出来的代码路径。插件安装器并不是一个有文档的偏好项,而是一条脚本化的升级路径。你无法关闭一个你根本看不见的开关。
- 信任决策发生在应用之外。批准钩子的尝试发生在Codex的配置里,而不是 Postman 里。即便源应用的设置面板表现完美,也不会暴露这件事。
更深层的失败是结构性的:退出选项把警惕的负担放在了信息最少的一方身上。厂商知道更新器能做什么,你不知道;厂商知道应用会回连哪些端点,你只能猜。任何需要信息较少的一方持续跑赢信息较多的一方的控制手段,迟早会失效。这也是为什么 IDE 插件市场的信任问题 总是反复咬人:你以为自己在管理的那道安全边界,根本不是真正被跨越的那一道。
厂商信任并非毫无价值,只是它不能成为控制手段。它是决定安装什么的一个因素,而不是让已安装软件保持诚实的机制。想通这一点之后,审计策略就水到渠成了:从外部持续验证行为,使用厂商无法控制的工具。
为什么一次性安全审计也会失效
第二种失败更体面一些:时点式审计。安全团队审查工具,检查网络调用,阅读隐私政策,给出批准,归档报告。完成,直到自动更新跑了一遍。现在的桌面开发工具按周甚至按天发版,而功能开关意味着版本号相同的两个用户,实际运行的代码可能大不相同。这次事件的行为正是被服务端开关和“存量用户”条件控制的,这意味着安全团队在全新环境中审查的安装包可能看起来干干净净,而你的老用户却收到了插件推送。
我就曾经在这个缺口上栽过跟头。多年前,我在一次彻底审查后批准了一个同步客户端:流量抓包干净,权限合理,没有持久化机制。六个月后,一次更新加入了一个 LaunchAgent 和一个 socket 监听器。我们之所以发现,是因为一台 VPN 不稳定的笔记本在防火墙日志里不断出现诡异的连接重试。没人重新审查过任何东西,因为没有任何流程要求这么做。那次批准是一张快照,而软件是一部电影。
所以两种廉价的做法都行不通:信任厂商的设置行不通,因为真正有意思的行为并不在设置里;一次性审计也行不通,因为真正有意思的行为是在审计之后才发布的。可行的做法是把你自己机器的状态当作事实来源,并持续观察它的变化。这比采购评审枯燥得多,但它捕捉的是现实,而不是意图。

如何真正审计桌面开发工具
可行的方法分三步:快照关键状态,监视网络流量,并遏制你无法完全验证的部分。这些都不需要企业级工具,只需要纪律,再加一小时左右的设置时间。
通过配置快照检测静默插件安装
你电脑上的每个编码代理和开发工具,都有一小组文件定义了它的行为:~/.codex/config.toml、~/.claude/settings.json 及其插件目录、MCP 配置、shell rc 文件、~/Library/LaunchAgents、~/.local/bin。这些文件就是同意的边界。任何未经询问就修改它们的行为,正是你想要捕捉的东西。所以每天对它们做哈希并比对差异:
#!/usr/bin/env bash
# snapshot-agent-configs.sh — hash every file your agents and shells read,
# then diff against yesterday. Run from cron or launchd.
set -euo pipefail
SNAP_DIR="$HOME/.config-audit"
mkdir -p "$SNAP_DIR"
TODAY="$SNAP_DIR/$(date +%F).txt"
find \
"$HOME/.codex" \
"$HOME/.claude" \
"$HOME/.config" \
"$HOME/.local/bin" \
"$HOME/Library/LaunchAgents" \
-type f 2>/dev/null \
| sort \
| xargs shasum -a 256 > "$TODAY"
PREV=$(ls -t "$SNAP_DIR"/*.txt 2>/dev/null | sed -n '2p' || true)
if [ -n "${PREV:-}" ]; then
if ! diff -u "$PREV" "$TODAY" > "$SNAP_DIR/latest.diff"; then
echo "config drift detected — see $SNAP_DIR/latest.diff" \
| mail -s "agent config drift" you@example.com || \
cat "$SNAP_DIR/latest.diff"
fi
fi
这个方法很粗糙,但没关系。如果在报告者的机器上跑着这套东西,Codex 的 config.toml 改动、新增的 Claude 插件记录,以及 ~/.local/bin 里的 CLI 二进制,都会在事发次日早上的一次差异里全部亮起来。信噪比非常好,因为这些目录平时都很安静。除了你自己的编辑之外,没有其他合法改动,而你自己什么时候改过,你是清楚的。再配合在同一快照里加上 launchctl list | grep -v com.apple 的输出,持久化机制一出现,你也能同时发现。
监控网络外发,而不只看文件
文件完整性只能告诉你磁盘上改了什么,却不能告诉你什么数据离开了这台机器。要看外发流量,你需要按应用划分的可见性。在 macOS 上,Little Snitch 或开源的 LuLu 防火墙都能按进程对新的出站连接发出警报:当 Postman 的更新器或一个新的 CLI 二进制第一次连接一个陌生主机时,你会收到提示,而不是既成事实。在 Linux 上,opensnitch 能完成同样的工作。如果你只想手动抽查一个“话多”的应用此刻在做什么:
# Every established outbound connection, grouped by process
sudo lsof -iTCP -sTCP:ESTABLISHED -P -n | awk '{print $1, $9}' | sort | uniq -c | sort -rn
# Watch one vendor's traffic live (find the process name first)
sudo tcpdump -i any -n host mcp.postman.com
有两件事值得留意:与该工具声明功能无关的目的地,以及应用空闲时仍然保持的连接。HTTP 客户端在你没有点击发送时应该是安静的。遥测端点本身并不会自动定罪,但正如前面那个 HIPAA 调查故事所示,关键在于载荷。如果你的客户端在本地解析 {{secrets}},同时在服务端记录完整的请求细节,那么“匿名使用数据”就不是匿名的。唯一的验证方法是抓包并阅读,最好是在一次你有意把金丝雀令牌(一个独特的假凭证)加载到环境变量里的会话中,观察它是否会流向不该去的地方。金丝雀令牌能把“我们觉得他们可能这么做”变成“我们知道他们确实这么做了”。
遏制你无法完全信任的部分
最后一步承认你不可能抓住所有问题,所以要限制爆炸半径。把涉及大量凭证的工具放进专用的操作系统用户账户或轻量虚拟机中运行,让它们的文件系统访问止步于沙箱边缘。macOS 和 Linux 都能很方便地创建第二个用户,而 Linux 上的 firejail 这类工具还能针对每个应用禁止整棵目录树。把真正的生产密钥完全排除在桌面工具之外:使用短期令牌、范围受限的服务账户,以及会过期的请求签名。如果某个工具必须依赖长期有效的生产凭证才能工作,那这个要求本身就是一项发现。我们之前也论证过,当漏检的代价很高时,遏制优于监控。对于代理以及用来配置代理的工具而言,当你的监控出现盲区的那一天,遏制仍然是那一层还能起作用的防线。
工具事件的回滚操作手册
一旦你确实发现了问题,卸载违规应用只是大约六个步骤中的第一步。Postman 那位报告者的移除清单形态很对,值得在真正需要之前先内化:
- 先保留证据。在卸载之前,把日志、配置和版本号复制到安全的地方。Postman 自己的 main.log 就是这里的关键证据,卸载脚本总喜欢把它删掉。
- 移除应用及其辅助程序。包括应用包本身,以及它在
~/.local/bin、Application Support 和缓存目录中留下的任何东西。 - 清理代理配置。从每一个代理宿主中删除 marketplace 条目、已启用插件的配置段、MCP 服务器定义和缓存的插件目录,而不仅仅是最显眼的那一个。
- 撤销信任记录。删除所有你未批准的钩子对应的
trusted_hash条目。然后检查同一文件中的其他钩子:如果有一个被偷偷塞进来,就要检查其余的。 - 检查持久化机制。LaunchAgents、LaunchDaemons、登录项、cron 条目、shell rc 中的新增内容。
- 轮换经过它的凭证。任何存在于该工具环境或集合中的凭证都应视为已泄露。轮换很麻烦,但事故响应只会更麻烦。
- 确认移除彻底。让你的快照脚本再运行几周。换个开关重装的做法,是一种已知模式。
清单里没有写的是:提交工单然后干等。这一步也要做,公开的、证据充分的报告是我们其他人得知问题的方式,但你自己的安全卫生不能依赖厂商的路线图。

开发者工具安全的更大教训
很容易把这个故事仅仅当成某一家公司的发展轨迹来讲:一款备受喜爱的轻量工具,融资数亿美元,招募了大批人手,如今必须用登录、订阅和“一切皆 AI”来证明数十亿美元的估值。这个叙事令人满足,大体也属实,但它让我们其他人轻松地置身事外。同样的激励结构如今也潜伏在你电脑上一半工具的底层。每个有 AI 故事要讲的厂商,都想把自己的插件放进你的代理,把自己的 MCP 服务器写进你的配置,把自己的钩子放进你的会话启动。有些会先征求你的同意。证据表明,至少有一家认定征求同意是可选项。
市场的反应已经很明显,而且很有启发性:Bruno、Kreya、Yaak 这类离线优先、以 git 为原生的 API 客户端正在走红,正是因为它们的信任模型清晰可读。文件在你的仓库里,请求从你的机器发出,无需账号。当一个工具的存储格式只是一个文本文件夹时,审计它就是 git diff。这不是怀旧,而是一种安全架构。LiteLLM 供应链事件从包注册表这一侧教了同样的教训:你能检查并锁定版本的产物,才是你能够为之辩护的产物。
藏在功能开关里的同意不是真正的同意。如果一个工具可以在不改变版本号的情况下改变自己的行为,那么你对它的批准就毫无意义。
所以我要坚持的立场是:别再按功能评估桌面开发工具,改为按可逆性来评估。它写入的所有东西我都能看到吗?我能比对它的配置吗?我能在没有账号、离线、并且处于一个会记录日志的防火墙后面运行它吗?我能彻底移除它吗,包括它在自己目录之外碰过的东西?能经得起这些问题拷问的工具,往往很朴素,而朴素正是你希望横在凭证与网络之间的东西。那些经不起拷问的工具,无论自动补全有多好,都应该被关进沙箱,或者干脆移出这台机器。你的 API 客户端就是凭证保险箱。请开始像对待保险箱一样对待它,因为厂商显然早已这么做了,只是方式不是你希望的那种。


