一条 npx 命令引发的越界问题
案例:为什么我的每一个agent工具里都被安装了这个skill
最近一个朋友跟我讲了一个很典型的 Harness Engineering 的问题:
"他使用npx skill add命令从github上安装了一个 skill 之后,发现这个 skill 不只出现在当前使用的 Agent 工具里,还出现在了机器上的其他 Agent 工具里。本来以为只是给 Codex 装一个 skill,结果 Claude Code、Cursor 或其他支持 skill 的工具目录里,也出现了这个skill。"
我听到之后第一反应就是: "这个 skill 是不是藏了恶意的脚本,用户下载之后就开始自动传播?”
抱着这个想法我去github上看了一下这个代码库. 哟嚯,这个skill有 5.6K 的star, 700+的fork. 看完之后我发现真正的问题不在 skill 文件本身,而在代码库 README 里推荐的一条安装命令。
这条命令是这样的,为了不给任何人打广告,我就不贴原文了:
npx -y skills add xxx/xxx -g --all当我自己看到这条命令时,我第一反应也是:安装这个项目的全部 skill。但实际上当我把这个代码库丢给我的codex,和它描述了我朋友的问题,要求codex追查问题的时候,我发现我对这个命令的理解错了:
我们来看这条命令里的三个参数:
-y
-g
--all-y 的意思是跳过确认。
-g 的意思是全局安装。
--all 在这里的意思不是“安装这个项目里的全部内容”这么简单。它在 skills 这个安装器里的真实含义是:把这个 skill 安装到它支持的全部 Agent 工具目录里。
也就是说,用户以为自己在做的是:
给当前 Agent 安装一个 skill实际发生的是:
把这个仓库里的 skill,全局安装到本机上多个 Agent 工具目录里,并跳过确认这也不是传统意义上的病毒。代码里也没有看到它偷偷下载后门,也没有看到它去读取 token、注册启动项、复制剪贴板内容。
而真正的问题是: 这个代码库的 README 里的安装说明本身越界了。
README 把一个影响范围很大的命令,包装成了普通安装命令。用户没有被告知这条命令会影响哪些工具,也没有机会在安装前确认写入路径。这本身在软件工程领域就是一个极其严重的问题
问题的根源是skills add这条命令
如果我们把 npx -y skills add xxx/xxx -g --all 这条命令拆开:
npx 的作用是运行一个 npm 包里的命令。比如:
npx skills add xxx/xxx意思是临时运行名为 skills add 的 npm 命令,让它去安装某个 skill 仓库。
在这个案例里,skills 不是被安装的那个 skill,而是一个第三方安装器。被安装的内容是后面的 xxx/xxx 仓库。
所以这里有两层东西:
skills CLI:负责安装
xxx/xxx skill 仓库:被安装的内容真正执行写入目录、选择 Agent、处理 --all 参数的,是 skills 这个 CLI。
这个 CLI 本身支持很多 Agent 工具,比如 Claude Code、Codex、Cursor、Gemini CLI、Windsurf 等。它内部有一张表,记录每个 Agent 的 skill 目录在哪里。
例如:
Claude Code -> ~/.claude/skills
Codex -> ~/.codex/skills
Cursor -> ~/.cursor/skills当命令里写了 --all,它就不是只选择当前正在使用的 Agent,而是选择它支持的全部 Agent 类型。
这就是很多人容易误解的地方。
--all 的真实含义
普通用户看到 --all,很容易理解成“安装全部 skill”。
但在这条命令里,--all 的真实效果更接近:
安装全部 skill 到全部 Agent,并跳过确认它等价于:
--skill '*'
--agent '*'
-y这三个东西合在一起,风险就变大了。
--skill '*' 表示安装仓库里的全部 skill。
--agent '*' 表示目标是安装器支持的全部 Agent。
-y 表示不要再问用户,直接执行。
再加上 -g,就变成全局安装。
所以这条命令完整展开以后,不是一个小范围安装命令,而是一个全局、多工具、无确认的批量写入命令。
这就是为什么用户会觉得“它怎么跑到我其他 Agent 工具里去了”。
不是因为 skill 自己会传播,而是因为安装命令要求安装器这样做。
工程边界在哪里被打破了
这个案例最严重的地方,它打破了工程边界。
用户的边界是:
我正在当前 Agent 里安装一个工具。命令实际执行的边界是:
我正在改写本机上多个 Agent 的全局配置。这两个边界完全不是一回事。
当前 Agent,是一个很小的范围。本机所有 Agent,是一个很大的范围。
当前项目,是一个更小的范围。全局目录,是一个长期生效的范围。
README 里的安装命令,把这些范围全部混在一起了。
如果这个问题发生在我以前工作的公司,如果是 QA 发现这个问题,毫无疑问是 P0 的 BUG. 如果被客户发现这个问题, 连 RD 带经理一起处理,全团队还要开复盘. 简直要被永远钉在耻辱柱上。
在 Agent 工具里,这个问题的风险性更大。因为 skill 不是普通库文件。普通库文件一般只有在代码显式调用时才会执行。skill 更像是一层给 Agent 看的行为指令。它会影响 Agent 如何理解任务、如何触发流程、如何写文件、如何调用工具。把一个 skill 写进多个 Agent,相当于同时改变多个 Agent 的工作环境。如果用户在安装之前就被告知这件事,并且明确同意安装,那没有问题。但问题是用户不知道啊,我翻遍整个 README 也没看到这个代码仓库的作者对这条安装命令有任何安全提示. 我十分怀疑这个readme也是用 AI 写的,作者根本就不看.
我们深入的问一句: 这是恶意的吗?
从目前我们看到的证据,不能直接说这是恶意传播。
因为真正的安装行为来自第三方 skills CLI。--all 是这个 CLI 公开支持的功能。被安装的 skill 仓库并没有自己写一段脚本去扫描所有 Agent 目录,然后偷偷复制自己。但不能因为不是恶意,就说问题不严重。
软件工程里有绝大多数事故,并不是开发者有主动"作恶"的意图. 而是质量的 BUG. 今天这个问题的性质更接近:
文档诱导型越界 bug也就是说,代码功能本身是公开的,但 README 推荐的使用方式会把用户带到一个远超预期的作用域里。
这应该按严重问题处理。我甚至认为,在 Agent 工具生态里,这类问题可以按 P0 级别看待。它改的不是一个项目目录,而是用户本机多个 Agent 的全局行为环境。它还通过 -y 跳过确认,让用户没有机会在执行前看到影响范围。
两个库分别负什么责任
这里有两个责任主体。
第一个是 skills CLI。
skills CLI 是一个第三方 npm 安装工具,包名叫 skills(你没看错,这个 CLI 的名字就叫skills). 代码仓库在github的 https://github.com/vercel-labs/skills。从仓库归属看,它由 Vercel Labs 维护. skills CLI 是一个 Agent skill 的安装和管理工具。它的具体功能包括:
• 从 GitHub 仓库、npm 包或本地目录里寻找 SKILL.md 文件。 • 读取 skill 的名称、描述和目录结构。 • 按用户指定的目标,把 skill 安装到不同 Agent 工具的 skill 目录里。 • 支持 Claude Code、Codex、Cursor、Gemini CLI、Windsurf 等多个 Agent。 • 提供安装、删除、列出、搜索、更新、初始化等命令。
它定义了 --all 的含义,也维护了 Agent 支持表。它知道哪些 Agent 会被写入,知道每个目录在哪里。作为安装器,它应该对高影响操作有更强的提示。
比如:
你即将安装到 50 多个 Agent 目标。
你即将写入以下全局目录。
这不是只安装到当前 Agent。
是否继续?更好的做法是,--all 不应该默认跳过这些提示。即使用户传了 -y,涉及全局、多 Agent 的写入,也应该有特殊保护。
第二个是 使用这条命令: npx -y skills add xxx/xxx -g --all 的skill仓库作者。
他在自己的 README 里推荐了:
npx -y skills add xxx/xxx -g --all这就是他要负的责任,而且我得说这种行为非常的不负责:README 面向的是用户。用户不会先去读第三方 CLI 的源码,也不会知道 --all 在这个上下文里到底是什么意思。代码库的作者要为自己写的每一个字负责, 但是他把这条命令放在“通用安装方式”下面,普通用户就会认为这是安全、推荐、默认的安装方式。
更合理的写法应该是:
npx skills add xxx/xxx --list先列出可安装内容。
如果用户只用 Codex,就写:
npx skills add xxx/xxx -a codex如果用户只用 Claude Code,就写:
npx skills add xxx/xxx -a claude-code是否全局安装,应该单独说明。是否安装全部 skill,也应该单独说明。是否跳过确认,更不应该作为默认推荐。
我们应该怎么防
这个CASE给我的最大提醒是:
不要盲目相信github上的star, fork. 也不要相信把代码上传到github上的人就对自己的代码完全了解.
尤其是看到下面这些组合时,必须停下来:
npx
curl
wget
-g
--global
-y
--yes
--force
--all
*这些词单独出现不一定危险,但如果组合在一起,就说明这条命令可能会拉取外部代码、跳过确认、全局安装、批量写入。
这时候应该先做静态调查。
比如:
这个命令背后运行的是哪个包?
这个包是谁维护的?
--all 到底是什么意思?
-g 会写到哪里?
-y 跳过了哪些确认?
会不会影响多个 Agent?
有没有回滚方式?如果查不清楚,就不要执行。
对 Agent 来说,这应该变成 harness 规则,而不是靠人记住。
只要命令同时命中:
外部执行入口
全局安装
跳过确认
全量目标
Agent 配置目录就应该默认拦截,先审计,再决定是否执行。
最后
这个案例暴露出来的问题很扎心:在 AI Agent 时代,不能把信任交给除了规则和铁律以外的任何形式
以前我们看一个github上的代码库,如果有 4 位数的star, 700+的fork,我们默认是"有这么多的程序员认可", 也只有从事软件研发的人员才会去安装,fork开源代码库. 到了 AI 时代,agent的coding能力极大的扩展了github的受众群体,我们现在只能认为"有这么多 AI 的使用者对这个代码库感兴趣".
过去我们读 README,会默认它是项目作者认真写下来的说明。这个默认信任在今天已经不可靠了。现在大量 README、安装说明、配置教程,可能都是 AI 生成的。AI 生成内容最大的问题,不是它一定会胡说,而是它经常把“看起来合理”的东西写得很顺滑。命令格式对了,语气也像官方文档,但里面的边界、权限和后果,可能根本没有被认真审查过。
唯一稳妥的做法,是保护好自己的电脑和服务器。这就意味着每一个 AI Agent 的用户必须要建立起自己的harness。它应该在命令执行前拦住高风险组合,比如今天我们看到的:
npx / curl / wget
-g / --global
-y / --yes / --force
--all / *
~/.claude / ~/.codex / ~/.cursor / ~/.agents只要命中这些组合,就先停下来,静态审计,再决定能不能执行。
这次事故的教训很朴素:
1.不要相信一条看起来正常的安装命令。
2.不要把 README 当成权威。
3.不要指望写 README 的人,或者生成 README 的 AI,替你保护你的机器。
4.真正可靠的做法,是在自己的 Agent 前面加门禁。先保护执行环境,再谈自动化效率。
夜雨聆风