跳到主要内容

这期选手不是 repo:agent 安全里真正难的那一半

小周
小周

· 阅读约 5 分钟

刷 dev.to 刷到快一点,本来在找别的,手指一滑点进 Carlos(Bloqarl)两周前那篇改造记录。这人在 Zealynx Security 做智能合约审计,日常就是把编码 agent 往真仓库里塞,塞完把撞上的东西写下来。

这期选手不是个 repo,一篇文章加一串评论区。但我摸了两遍——第二遍专门冲评论区去的——比这周刷到的大多数仓库都值得看。

一句话定调:它讲的是“你让 agent 跑个测试”这句话,到底授权了什么。

前半篇不用我多讲,同行早懂了。仓库里没有“项目文件”这回事,README、open issue、测试文件、package.json、CI 配置,对你是文件,对攻击者都是能往命令执行那头递话的入口。让 agent 跑测试,语义上等于执行测试文件里写的所有代码。

让我坐直的是他拿来类比的工具:污点分析。先把不可信来源到执行汇的每条路径画出来,再一条条问靠什么机制阻断。执行汇列得很具体——shell、包安装、仓库外写入、CI 变更、网络请求。

然后是最诚实的一句:他自己的经验里,这些路径上的阻断机制往往压根不存在,能给的答案通常是“只能靠我自己注意到”。

一个天天做审计的人写出这句,比任何一条防护建议都有信息量。

沙箱那段我一句带过:容器或 VM 里只挂仓库,碰不到家目录、SSH key、云凭证;环境里不放常驻密钥,真要用就按单个任务发临时低权限凭证。都对,也都不新鲜。

他关于审批的那条才是我抄走的。做法不稀奇——关掉全局自动批准,只读放行,安装、工作树外写入、动 CI 或钩子、发网络请求一律等人点头。稀奇的是紧跟着补的那句:前九个例行提示点过去之后,第十个真关键的也会被顺手点掉。所以他要的是提示少,不是提示严。

这个我服。我 agent 的审批白名单原本七条,看完砍到三条——白名单不是越长越安全,长到你懒得读的那天,它就是个摆设。

真正让我改观的是他把配置文件单独拎出来那一类。shell 配置、IDE 设置、git 钩子、CI workflow,共同点是一次误操作能活过这次会话。沙箱里跑得再干净,钩子写进 .git 里,明天你手动提交一次它就响了。

然后评论区就开始了。Edward Izgorodin 上来就把作者方案里最舒服的那条打穿:按任务开 git worktree,隔离的只是检出内容,钩子和配置跟主仓库共享。他在 git 2.52 上验了一遍,从任务 worktree 写进去的 pre-commit,会在主 worktree 下一次提交时执行。

隔离了,但又没完全隔离。

husky 那边更难处理:它把 core.hooksPath 指向 .husky,钩子体本身是受版本控制的文件,改一下 .husky/pre-commit,任何检出这个分支又装了 husky 的环境,下次提交就中。

有意思的是他接着自己推翻自己——就算不用这些,换回主仓库的钩子也没用,因为 husky 生成的 pre-commit 里面跑的是 npm test,跑的还是 agent 写进去的那堆测试脚本。

后面就刹不住了。git 2.54 起钩子能在配置里用 hook.<name>.command 声明,他在 Git for Windows 2.56 上看到全局配置里声明的 pre-commit,在 hooksPath 指向空目录时照样运行,core.fsmonitor 指定的命令也会在提交时执行。最后他给的提交姿势是:GIT_CONFIG_GLOBAL 指空文件、GIT_CONFIG_NOSYSTEM=1、先确认配置是空的、再 -c core.hooksPath=<空目录> -c core.fsmonitor=false。

一路看下来我反而踏实了。不是因为他解出来了,是这套东西长成这样本身就是结论:git 的配置面当不了安全边界,它太宽、太多层,每层都能声明要执行什么。要这么长一串咒语才能安全提交一次,正确答案是别让 agent 提交。Edward 自己也这么收的——agent 只编辑检出内容,提交交给外部的 harness,不给它这个权限。

这条我认为是整篇里最值钱的,原文没写,是评论区逼出来的。

Max Quimby 补的那种攻击我没想到。被注入的 issue 先诱导 agent 写一个安装脚本,agent 在后面的步骤里又把那个脚本当可信输入执行——注入和执行被时间隔开,单步看每一步都正常。所有以“每一步都审批”为前提的方案,在这个模式面前都是筛子。

他还追问了一句出站流量。作者回得很老实:现在完全靠人工审批,但“批准跑测试”不等于“批准测试发起的网络请求”,他期望最终改成默认拒绝、只放行包仓库。这句我认,因为我自己的配置就停在他现在这个状态。

依赖那块顺手记一笔。对新增包,装之前核三件事:存不存在、是不是你要的那个、谁发的。slopsquatting 这词就是为第一条造出来的,模型容易凭空幻觉出包名,攻击者抢注。lockfile 必须进库,不然 lock 了等于没 lock。

最后一条是合约场景的硬规则:让 agent 完全滚开 deployer 和 signer 的密钥。理由特别具体——一个“只部署到测试网”的脚本,改一个 RPC URL 就指到主网了。这种脚本我见过一堆,写的时候都标着 testnet。

我这两天的动作很小:摘掉 agent 的提交权限,只让它改检出内容;白名单从七条砍到三条;lockfile 该进库的进库。出站默认拒绝我还没做,作者也没做。候选池里搁着一个做 agent 沙箱的,等它把 macOS 那边的实现补齐再讲。

这套值不值得抄,先去看一眼你自己 .git/hooks 和 core.hooksPath 长什么样,比读十篇安全清单管用。