你没法审查一个技能。你只能审查它的某一个修订版。
一个技能文件夹没有版本字段,所以「我们审过那个技能」不是任何人能核对的说法——它描述的只是有人去看的那一天那个文件夹里装着什么,而发布者此后可以改掉它,且不改动任何你会注意到的东西。规模最大的那份已发表审计,在公共注册中心上约四分之一的技能里标出了危险模式,而这个基础率足以击败抽检。所以审查的单位必须是一个你自己留了副本、并且钉住了的修订版——因为你可能想要的其他每一项性质(作者、扫描结果、能力)要么根本不在这个格式里,要么在文件夹一移动就过期。
步骤 1两个可执行面,而多数审查只读其中一个。
一个技能就是一个文件夹:一份装着指令的 SKILL.md,加上那些指令指向的脚本与参考材料。两半都会执行,由不同的解释器执行,而人们从软件安全带过来的审查习惯,瞄的是错的那一半。
- 脚本在 shell 里执行,带着智能体自己的权限,在指令叫智能体去跑它们的时候。这一半看起来像代码,所以这一半会被读。下文那份规模研究发现,随包带可执行脚本的技能,含有漏洞的可能性是纯指令型技能的 2.12 倍(比值比 2.12,p<0.001)——所以这份直觉不算错,只是不完整。
- 散文在模型里执行,而模型是为遵循指令而造的,分不出哪一条是作者出于好意写的、哪一条不是。这一半外面没有沙箱、没有可审计的语法,也没有任何看起来可疑的 diff。
迄今有记录的最大一场战役走的是第二个面。2026 年 1 月下旬,Koi Security 审计了 ClawHub 注册中心上的 2,857 个技能,查出 341 个恶意技能;其中 335 个属于同一场战役 ClawHavoc,投递的是 Atomic Stealer 这个 macOS 窃密程序。载荷不是随包脚本里混淆过的代码。它是 SKILL.md 里一个伪造的「前置依赖」小节,叫智能体在开始任务之前先下载并运行一个安装程序——一个看起来再普通不过的准备步骤,写在那个全部存在意义就是「被读到并被照办」的文件里。
如果你的技能审查是「一位安全工程师扫一遍脚本」,那 341 个里有 335 个会被放过去。那些散文不是关于可执行部分的文档。它就是可执行部分,而且是没有任何称得上静态分析的那一部分。
步骤 2这个格式没有携带什么,以及为什么那决定了整个问题。
有三个本来能让审查变得可操作的字段,在技能文件夹里是缺席的,而它们的缺席不是疏忽——它来自这个格式的目标:同一个文件夹,要在每一个实现该标准的宿主里一模一样地工作。
- 没有版本。没有数字可以递增,于是一次审查没有东西可以附着。「已审过」变成一句关于某个时刻的陈述,而文件夹里没有任何东西记下是哪个时刻。
- 没有发布者身份。文件夹不会以任何可被验证的方式说出是谁写的。现存的那点归属信息活在它外面的注册中心或仓库里——那是另一个系统、另一套访问控制,而一个被复制走的文件夹把它留在了身后。
- 没有能力声明。没有任何地方写明这个技能预期会碰哪些工具、哪些网络目标或哪些文件,于是宿主即便想按最小权限授予也做不到。技能就这么继承了智能体已经持有的一切,而这正是环境权限这个问题换了条路径到场。
对比一台 MCP 服务器,三样它都有。一台服务器是一个 URL,背后有身份,另一端有运营方,于是宿主可以钉住端点、校验令牌的 audience,并用一个立刻生效的动作撤销访问。而一个技能是某人复制的一个文件夹。没有端点可以取消授权、没有令牌可以吊销、也没有运营方可以通知——只有一些早已躺在某个仓库里的文件,等着下一个与那行描述匹配上的智能体来加载。技能、子智能体、MCP 还是钩子梳理的是你究竟为何会选技能;这里要说的是:选了它,就等于选了撤销把手最少的那个机制。
有一个后果值得直说,因为各团队总在事后复盘时重新发现它:决定一个技能会不会被加载的,是它那行描述,而不是它的内容。一个智能体从不匹配的技能是惰性的;而一个描述写得宽泛的技能,会在没人预料到的任务上被加载。智能体技能论证过那行描述是「有用性」的承重字段。它同样是「暴露面」的承重字段——一行散文,而没有哪份审查清单把它当成与安全有关。
步骤 3在设计流程之前,先把基础率读明白。
《Agent Skills in the Wild》(arXiv 2601.10338)在 2025 年 12 月抓取了两个技能市场,收集 42,447 个技能,去重后分析了 31,132 个,外加 3,574 个随包的可执行脚本。它的检测器 SkillScan 把静态分析与 LLM 分类配在一起,并以 500 个人工标注的技能做校准。结果是:26.1%——8,126 个技能——命中了十四种危险模式中的至少一种,其中 12.8% 为低严重度(做法糟糕)、8.1% 为中(意图含混)、5.2% 为高严重度(提示存在恶意意图)。
请仔细读那个 5.2%。它是一个检测器给出的严重度判断,不是经确认的恶意软件计数;任何把它引用成「每二十个技能里就有一个是恶意的」的说法,都是把分类器输出提拔成了结论。站得住的版本更窄,而且依然决定性:在一个大规模公共语料上,一个基于模式的检测器会在大约四分之一的技能里找到值得人看一眼的东西,并把大约二十分之一放进它最严重的那一档。在这个率下,建立在抽检之上的流程会持续漏;而建立在「我们只装热门的」之上的流程,倚靠的是安装量这个信号——而 ClawHavoc 证明了它制造起来很便宜。
有用的重构是:这些数字并不给出「摆在你面前这个技能是敌对的」的概率。它们告诉你的是,这个语料足够大、下限足够低,以致你的保护不可能来自「被发布出来的东西的平均质量」。请像对待任何用户上传内容的索引那样对待一个技能注册中心,因为它就是。
步骤 4把修订版当作单位,审查就变成了一个 diff。
上面这一切指向同一个流程改动,而它很便宜。别再按名字从注册中心装技能,改成 vendor 进来:把文件夹复制进一个你控制的仓库、记下一个提交,并把此后每一次更新都当成一个拉取请求。这正是钉版本与校验在别处一直主张的那个动作——名字要经过别人的命名空间才能解析,而提交本身就是内容——而对技能来说,这是让「已审过」这句话有意义的唯一办法,因为它补上了这个格式缺的那个版本字段。
它具体买来什么:第二次审查成了一个 diff,而不是重读一遍。一个技能的第一次审查是实打实的工作;第二十次更新是四行改动的散文,评审者真的评得过来。没有那个钉,每一次更新要么是一次未经审查的变更,要么是一次完整重读——而实践中是前者。
然后把第一次审查做得足够机械,好让人真的会去做。枚举比判断更要紧,因为失效方式不是「看错了一个文件」。而是「不知道那个文件在那儿」。
# Enumerate the surface before reading anything. Three questions: # what executes, what it reaches, and what the prose tells the agent to run. # 1. Everything that is not prose or reference material find skill/ -type f ! -name "*.md" ! -name "*.txt" -printf "%M %p\n" # 2. Every network destination mentioned anywhere in the folder grep -rEoh "https?://[^ )\"]+" skill/ | sort -u # 3. Every imperative in the prose that asks for execution or install grep -rniE "curl|wget|pip install|npm i|chmod|sudo|prerequisite|before you" skill/*.md # 4. The one line that decides whether any of this is ever loaded head -20 skill/SKILL.md
四次阅读,按这个顺序,而每一次都带着一个处置动作:
- 那行描述。它是被收窄到你想要的那个任务上,还是宽到会匹配上你从未打算过的工作?一个「帮我处理文件」也说得过去会匹配上的描述,就是一张常备邀请函。在你 vendor 过来的副本里把它收窄——这个文件夹现在是你的了。
- 那些散文,当成写给一个顺从进程的指令来读。任何「取下来再运行」的步骤都是 ClawHavoc 的形状,需要一个说出口还站得住的理由。「先装一下我们的 helper」不是。
- 那些目标地址。文件夹里出现的每一个主机,你都该说得出一个用途。一个解释不清的域名本身就是结论;你不需要证明它干了什么。
- 那些脚本。最后读它们,因为到这时你已经知道散文声称它们做什么了,而有意思的缺陷就在两者的分歧处。
第一次安装请在一个没有凭据、带出口允许清单的沙箱里跑,然后看它试图触达什么。一个行为与描述相符的老实技能,只要一次干净的运行就能确认;一个不相符的,会把那处不符显示在拒绝日志里——而这是比逐行读完便宜得多的学法。沙箱与隔离模式讲了值得用的那几层。
步骤 5能力熬得过变更。审查熬不过。
一个钉住的修订版解决了审查问题,却留下一个更难的:你仍然得判断对。扫描和阅读都是关于内容的判断,而你今天判断对了的内容,明天可以在任何你没钉住的副本里被替换掉。那道不依赖「你判断得对」的控制,是对一个被加载的技能究竟能触达什么设一个上限。
三道上限承担了几乎全部价值,而它们正是把任何一条指令变成一场事故的那三样:
- 网络出口。给智能体的出站流量加一份允许清单,就把「取下这个安装程序并运行」变成一次失败的请求,把「把文件发到这里」变成一条拒绝日志。这是这个面上单项杠杆最高的控制,而智能体的出口控制是它的实现篇。
- shell 与执行。如果那个跑第三方技能的智能体起不了进程,那随包脚本这半边问题就直接消失了。多数真正需要「做法」而不需要「执行」的技能,什么也没损失。
- 凭据触达。技能继承智能体的凭据,所以该问的是那些凭据是什么,而不是技能要求了什么。请通过一个代理去引用密钥,而不是把它们放在循环读得到的地方——见智能体的密钥管理。
三道合起来,你就得到了那个状态:一个敌对技能只是浪费了一个回合,而不是一场入侵——而这是唯一一个对「文件夹发生了变化」稳定的状态。顺带注意,这也是从另一头读到的致命三元组:不受信内容加凭据加一条外泄通道,而第三方技能是让第一条腿到场的一条特别直接的路,而真正起作用的是把另外两条里的任意一条去掉。
请诚实地算代价。给一个要浏览网页的智能体加出口允许清单是实打实的工作,而且第一周就会弄坏一些东西。「禁 shell」对一个文档类技能是免费的,对一个构建类技能则不可能。建议不是「全锁死」,而是「按技能类别把这三样定一次」,因为另一个选项是「按技能去定,然后永远不定」。
步骤 6三个数字,以及什么时候这一切都不值得做。
能让这件事保持诚实的度量很小,而且没有一个是扫描结果。
- 清单完整度。你能不能列出你运营的每一个智能体可加载的每一个技能,以及每一个各自钉在哪个提交上?如果不能,那这本身就是全部结论,本页其余内容都还谈不上。生态级的安装量已经上到数亿;你自己那个数字是你真能知道的。
- 撤销耗时。从「这个技能有问题」到「没有任何智能体能加载它」,靠真做一次来测。对一个 vendor 过来的文件夹,这是一个提交;对那些由各开发者自己从注册中心装的技能,这是群里一条消息加一份祈愿——而这两者之间的差距,才是值得往上汇报的那个数字。
- 更新审查率。上个季度的技能更新里,有多大比例真的走过一个有人读的 diff?如果诚实的答案很低,那么那个钉是装饰品,你倚靠的是发布者。
以及「几乎什么都不必做」的情形:一个你自己写的、放在你控制的仓库里、由一个没有凭据也没有出口的智能体加载的技能,不是一个供应链问题,不需要审查流程。一个只装着你自家约定的散文、跑在工具集触达不到任何外部的智能体里的技能,也一样。本页绝大部分讲的是第三方那个情形——而把问题缩小的最快办法,是留意一下你有多少技能原本并不需要从别处来。工具目录的生命周期是数量长起来之后相邻的那门功课。
这周做两件事。把你的智能体能加载的每一个第三方技能,vendor 进一个仓库、钉在一个提交上,并把清单记下来——单这一个动作,就把一个无界的审查问题变成了一个有界的 diff 问题,并顺带免费给了你上面那个清单数字。然后挑你最忙的那个智能体,给它加一份出口允许清单,因为那是唯一一道在「某个钉住的文件夹原来从一开始就有问题」那天仍然有效的控制。这个格式的治理那一侧正在动——Agent Skills 已于 2026 年 10 月 22 日进入 Agentic AI Foundation,带版本的发布与安全指引都在它提议中的路线图上,我们在十个项目,而没有一个是信任边界里写过——但路线图不是控制,而安装基数已经出货了。