EN FR ES PT DE AR 中文

AI编程智能体撤走了最后一名人工审查员,供应链风险现在归你承担

智能体式代码生成正悄悄把依赖审查的权力,从工程师手中移交给根本不做审查的机器。解决之道不是逃离某种语言或某个仓库,而是把智能体能执行的一切关进笼子。

问一位工程师,上周上线的功能里用了哪些第三方包,过去你能得到确切答案。现在如果代码是智能体写的,你多半只会得到一个耸肩。包是智能体加的,包管理器拉取了它的全部依赖,而这中间没有人读过任何一行,因为“阅读”这件事,从来就不在这类工具主打的工作流里。这正是AI编程智能体供应链安全问题最尴尬的核心。

有必要把“智能体做的”这句话拆开看,因为它其实藏着三套彼此独立的机制。模型驱动的工具判断需要某个库,写下引入语句;紧接着,你的包管理器把这一个包名展开成一整棵你从未挑选过的传递依赖树;编辑器插件负责执行安装,常常还顺带跑测试,一切只是在开发环境里“试一下”的副作用。人工挑库时,好歹会扫一眼仓库主页、下载量、最近一次提交记录;解析器什么都不看,插件则会不假思索地执行解析器给出的结果。三层之外的包,得到的审查程度和这条链路给它的一样,也就是零。

并非所有智能体都是如此,这个限定很重要。不少工具在执行每条shell命令前仍会停下来征求确认,厂商文档也写得很清楚:Cursor智能体的文档说明其自动运行设置可以在不经确认的情况下执行白名单内的命令;Anthropic Claude Code的文档也显示,产品在默认的逐步确认之外,还提供自动接受模式和权限白名单。有据可查的事实是,这类免人工干预的模式确实存在,而且正是被当作“更快”来销售的卖点。至于有多少团队实际长期开着这些模式,没有任何厂商公布过数据,所以“人工审查降到零”这句话应被当作一种论证,而非一个已测量的事实:它是这些模式运作方式的逻辑推论,不是调查得出的结论。

没人审查的包一旦是恶意的,会发生什么?

这种情况已经发生过,而且数字还在攀升。Sonatype《2024软件供应链现状报告》显示,一年内记录在案的恶意开源包超过512,000个,同比增长156%。这是一个计数,不是一个被精确测量的概率,但方向毫无疑问:恶意包在各大仓库里堆积得越多,任何一次依赖解析触碰到它的可能性就越高,即便没人能给出一个确切的赔率。这类包会藏在哪里?Endor Labs的依赖管理研究发现,约95%的高危依赖属于传递依赖,也就是开发者从不会亲手挑选的间接依赖。这项数据统计的是高危包,不是恶意包,衡量的是缺陷藏在哪里,不是攻击落在哪里。但两者共享同一个藏身之处:如果连无心之失都已经聚集在智能体悄无声息解析的那几层依赖里,那正是植入代码同样会藏身、同样不会被读到的地方。

一个已被记录在案的真实事件,让这一切变得具体可感。Snyk的安全研究人员描述过这样一个场景:一名开发者在测试Cursor编辑器的集成功能时,通过传递依赖拉取了一个被投毒的热门Python包副本。这段恶意代码在导入时写入了一个隐藏的启动文件并自动执行。而暴露它的原因近乎荒诞:载荷递归生成Python子进程,直到耗尽内存、拖垮整台机器才被发现。发现问题靠的是资源耗尽这个意外,而不是某个真正生效的防线。

循着机制往下推,结论并不好受。没有人需要疏忽大意。工具链的日常行为本身就足够酿成事故:智能体请求一个包,解析器加上了这个包依赖的东西,集成环境把它跑了起来。没有人挑选过这段恶意代码,三个自动化步骤就完成了从头到尾的全过程,其中没有一个环节会审查。

逃离某个包仓库,是错误的本能反应

出事之后,本能反应往往是不再信任整个生态,归咎于某个仓库,转投一门“名声更安全”的语言。这种反应误判了风险所在。风险从来不在某个仓库的招牌上,它藏在层层嵌套、从未被审查的代码深处,而每一个公开生态系统里都有这样的深处。Sonatype的恶意包统计横跨npm、PyPI等各大平台,换一个仓库,只是换了一个你不信任的招牌,那些没人读过的代码照样留在你的构建服务器上。

到底该如何把编程智能体关进笼子?

三项管控措施能解决大部分问题,而且没有一项是玄学或者停留在纸面上。它们瞄准同一个目标:不再纠结于判断某个包是否可信,转而限制一个不可信的包能做什么。

第一,构建环节默认拒绝所有出站流量。构建智能体需要访问一个包索引,或许还需要一个代码托管服务,但它不需要访问你的云平台元数据接口、内部网络,或者境外某台来路不明的服务器。在GitHub Actions上,这已经是今天就能部署的能力,StepSecurity的Harden-Runner默认阻断出站流量,并接受白名单配置:

# .github/workflows:阻断出站流量,只放行构建真正需要的目标
- uses: step-security/harden-runner@v2
  with:
    egress-policy: block
    allowed-endpoints: >
      pypi.org:443
      files.pythonhosted.org:443
      github.com:443

这样一来,即便某个载荷试图外传一份SSH密钥,它也无处可送,Harden-Runner还会把被拦截的请求记录下来,让这次尝试出现在运行日志里。你并没有去判断这个包是否安全,而是限制了它能造成的破坏。

第二,从已提交且带哈希校验的锁文件安装依赖,任何对不上哈希的一律拒绝。仅仅锁定顶层版本号是不够的,因为真正的危险藏在传递依赖树里。锁文件会记录每一个已解析包及其哈希值,这样被替换或投毒的版本就无法悄悄溜进来。现成的命令都已经具备:npm ci严格按照已提交的package-lock.json安装,pip install --require-hashes -r requirements.txt会拒绝任何哈希缺失或不匹配的下载,再加上npm的--ignore-scripts,可以阻断许多攻击都依赖的安装期代码执行。哈希锁定之后,一个被替换的副本不再是一次静默下载,而会变成一份人工或扫描工具在任何代码运行之前都会审查的差异记录:

# package-lock.json,出现在拉取请求里
   "node_modules/log-helper": {
-    "version": "2.4.0",
-    "integrity": "sha512-6b1e2c..."
+    "version": "2.4.1",
+    "integrity": "sha512-00d4af..."

没有锁文件,解析器只会默默抓取新版本,变化根本不可见。有了它,这次篡改就成了等待被审查的红线,而不是一次悄无声息的安装。

第三,对每一次依赖变更做自动扫描,发现无法解释的问题就拦下整个构建。这正是工具链已经追上来的地方。OSV-Scanner会把锁文件对照公开漏洞数据库逐一核对,而Socket这类服务会直接在拉取请求上检查每个新增包的安装脚本、混淆代码和异常网络访问。把它接入同一个沙箱环境,让它在每一次锁文件变更时自动运行:

# .github/workflows:发现已知漏洞或新增可疑依赖时,直接拦下这次拉取请求
- uses: google/osv-scanner-action@v2
  with:
    scan-args: "--lockfile=package-lock.json"

在这之上叠加一层模型分诊,我认为正是经济账终于向防守方倾斜的地方,不过这部分是论证,不是基准测试的结论:那些不经阅读就敢安装的模型,同样也能比人更快读懂一份依赖差异,标记出一个启动钩子,追踪出一条数据外传路径。这样用起来,攻击方把攻击自动化了,你就把审查也自动化了。上面这几项才是可以落地的核心,模型是加速器,不是沙箱和锁文件的替代品。

我们在每一次咨询项目里都贯彻同样的纪律,这也是我们持续发布的洞察文章背后的一贯逻辑:该由人判断的地方,把人留在决策链里;不需要人判断的地方,把机器关进围栏。

智能体不会被塞回盒子里,也不应该。但一个“从不阅读就安装”的团队,等于亲手拆掉了自己最后一道防线。答案不是去更信任那些包,而是给构建一个安全的运行空间,给依赖一份能让篡改现形的锁文件,再配一个会读懂每一次变更的扫描工具,让没有被读过的依赖,一个都进不了生产环境。

常见问题

AI编程智能体安装包,会比人工开发者更危险吗?

本质上是同一类供应链攻击,但在团队为求速度而开启的自动批准与后台安装模式下,暴露面被放大了:智能体会解析并执行传递依赖,几乎没有人去扫一眼其中任何一个,一旦某个包被投毒,风险可能在被发现之前就已经扩散开来。

遭遇依赖投毒事件后,应该换一门编程语言吗?

不必。每一个公开生态系统里都存在未经审查的传递代码,Endor Labs的研究显示约95%的高危依赖都落在这一层,换一门语言只是换了一个你不信任的招牌,并不能解决问题。真正该做的是用带哈希校验的锁文件锁定版本、核实来源可追溯性,并把执行环境隔离开来。

AI智能体能不能反过来帮忙防御恶意包,而不只是制造风险?

可以,而且能落地部署的核心并不玄乎:在Harden-Runner一类默认拒绝出站的沙箱里跑构建,再给每一次依赖变更接上OSV-Scanner或Socket这类自动扫描工具,标记出安装脚本、混淆代码和异常网络请求。模型可以比人更快地对被标记的差异做分诊,但这只是沙箱与扫描工具之上的加速器,不能替代它们。

相关阅读

本文由 Abyshire 专有编辑系统的 AI 编辑角色撰写,并经我们的团队审核。