Agent Skill 的安全问题,不只是提示词注入
- # ai-agent
- # security
- # skill
- # supply-chain
刚开始接触 Agent Skill 时,很容易把它理解成一份写给模型看的提示词。按这个认识,安装 Skill 似乎只是在本地多放一个 SKILL.md,最多担心里面有没有 prompt injection。
问题在于,真实的 Skill 往往不只有说明文字。它还可以带上脚本、依赖、配置和参考文件,再让 Agent 调用终端、读取项目、访问网络。Skill 里的自然语言决定 Agent 接下来做什么,代码则借用 Agent 已有的权限把动作真正执行出来。
这使 Skill 同时进入了两条供应链:一条是指令供应链,另一条是软件供应链。理解它的安全问题,不能只检查提示词,也不能只跑传统的代码扫描。
Skill 为什么改变了信任边界
普通应用通常会区分代码和输入。代码由开发者审查,外部输入被当成不可信数据处理。Skill 的边界没有这么整齐:第三方提供的 SKILL.md 本身就是指令,Agent 加载后会把它当成完成任务的方法。
一段恶意逻辑可以分散在不同位置。例如,SKILL.md 只要求运行一个看起来正常的初始化脚本,脚本再读取环境变量或凭证文件,最后把结果发到外部服务。单独看每一层,都可能像合理的工具行为;组合起来才会看到完整的数据外泄链路。
另一个问题是权限继承。Skill 通常没有独立的操作系统身份,而是借用 Agent 当前能用的工具。Agent 如果能读工作区、调用 shell、访问网络和使用已经登录的外部服务,Skill 也可能间接获得这些能力。它请求的权限和它实际能触达的权限,未必是同一回事。
所以更准确的判断不是“这份提示词有没有危险句子”,而是:这组指令、代码和依赖在当前 Agent 权限下,最终可能产生什么副作用。
真实生态里已经出现了什么
2026 年发布的预印本论文 《Agent Skills in the Wild》 收集了两个公开市场在 2025 年 12 月的 42,447 个 Skill,过滤和去重后分析了 31,132 个。研究团队用静态规则和 LLM 语义分类组成的 SkillScan 流水线,把风险归纳为四类:prompt injection、数据外泄、权限提升和供应链问题。
31,132 个样本中,有 8,126 个至少命中一种潜在危险模式,占 26.1%。其中 5.2% 命中了很可能带有恶意意图的高严重度模式;数据外泄和权限提升分别出现在 13.3% 和 11.8% 的样本中。
这里的统计口径很重要。论文自己也提醒,26.1% 混合了恶意行为、疏忽造成的不安全做法和意图不明确的模式。没有锁定依赖版本也会被计入,但它和主动窃取凭证显然不是同一级别的问题。因此,这个数字更适合读成“约四分之一的样本值得进一步复核”,而不是“约四分之一的 Skill 都是恶意软件”。
研究还发现,包含可执行脚本和被标记为存在漏洞之间有显著关联,优势比(odds ratio,OR)为 2.12。这同样不是说“带脚本就危险”,而是说明只阅读 SKILL.md 会漏掉一块更高风险的攻击面。代码越多、外部依赖越多,安装前要确认的内容也越多。
SkillSpector 如何检查一个 Skill
NVIDIA/SkillSpector 就是针对这个问题做的安装前扫描器。以下分析基于 2026 年 7 月 31 日发布的 2.5.1 版本。
它的主流程由 LangGraph 组织,真实调用链可以在 graph.py 里看到:
resolve_input
-> build_context
-> analyzers(并行)
-> meta_analyzer
-> finalize_inspection_ledger
-> report
resolve_input 先通过 InputHandler 把 Git 仓库、URL、zip、单个 Markdown 文件或本地目录整理成一个本地扫描目录。这个入口本身也做了一些防护,例如限制远程 host、阻止私有 IP,并在解压 zip 时检查路径穿越。这里处理的是“如何安全地取得待扫描内容”,还没有运行 Skill。
接着,build_context 遍历目录,把可读文件放进缓存,解析 SKILL.md 的 frontmatter,并记录哪些文件可能可执行。后续分析器读的是这份统一上下文,而不是各自在磁盘上重复寻找文件。
真正的检查由一组并行 analyzer 完成。当前 ANALYZER_NODE_IDS 注册了 23 个节点,大致可以分成几层:
- 静态规则检查 prompt injection、数据外泄、权限提升、危险下载、过宽触发条件等文本模式。
- Python AST 和污点追踪查找
eval、exec、subprocess,以及外部输入流向命令执行、文件内容流向网络请求等行为链。 - YARA 负责匹配已知恶意代码特征。
- MCP 分析器比较声明权限和实际能力,并检查 tool metadata 中的隐藏指令、Unicode 欺骗和描述与行为不一致。
- 语义分析器用 LLM 寻找正则较难识别的改写指令、渐进式欺骗和开发者意图偏差。
这种组合的价值在于,不把所有希望压在同一种检测方式上。正则很快,但不理解上下文;AST 能看到代码结构,却看不懂自然语言的真实意图;LLM 可以补语义判断,但输出不够稳定,也会增加成本和数据外发范围。
LLM 在这里不是第一道防线
SkillSpector 里的 LLM 有两个作用。三个语义 analyzer 会直接发现依赖上下文理解的问题;之后的 meta_analyzer 再按文件复核已有 findings,判断它是真漏洞还是误报,并补充意图、影响和修复建议。
后一个步骤更像审阅者,而不是从空白开始找问题。它收到文件内容、Skill metadata 和前序分析结果,再返回结构化判断。这样可以用语义信息降低误报,同时保留前面分析器提供的具体文件和行号。
如果使用 --no-llm,语义 analyzer 会跳过,meta_analyzer 则原样保留静态发现并补上默认修复建议。LLM 复核调用失败时,meta_analyzer 也会保留已有发现,不会因为模型不可用就把问题静默删掉。不过这个 fail-closed 行为只适用于复核阶段;语义 analyzer 没有执行,自然也不会产生只有语义分析才能发现的新问题。
是否启用 LLM 还涉及另一个安全边界。根据项目的 trust model,默认开启 LLM 分析时,待扫描文件内容会发送给配置的模型提供方。关闭 LLM 可以让文件内容留在本地,但已声明的依赖名称和版本仍可能被发送到 OSV.dev 查询漏洞。
这意味着用扫描器检查敏感的内部 Skill 时,也要先审查扫描器自己的数据流。安全工具并不会自动免除数据治理问题。
风险分数适合做门禁,不适合做证明
分析完成后,report 会生成 terminal、JSON、Markdown 或 SARIF 报告,并把 findings 压成一个 0–100 的风险分数。
当前实现给 CRITICAL、HIGH、MEDIUM、LOW 分别设置 50、25、10、5 的基础分,再乘以 confidence。同一条规则重复出现时,前三次按 1、0.5、0.25 递减,更多重复项不再加分;只有来自可执行文件的 finding 才会额外乘以 1.3。这样可以避免同一种低层模式因为重复很多次就把总分推满,也不会因为 Skill 里恰好存在脚本就放大所有文档发现。
最后,0–20 对应 SAFE,21–50 对应 CAUTION,51 以上对应 DO_NOT_INSTALL。CLI 也可以把这个结果接进 CI 或安装流程。不过默认 exit code 只在分数大于 50 时返回 1,CAUTION 仍然返回 0。如果团队希望中等风险也阻止安装,就要读取 JSON 中的 recommendation,而不是只看命令是否成功。
这个分数适合回答“接下来应该自动放行、提醒还是阻止”,不适合回答“这个 Skill 已经被证明安全”。零 finding 只能说明扫描器在自己的可见范围和规则能力内没有发现问题。只要可见范围、规则或运行路径有缺口,低分仍然可能是漏报。
安装前扫描看不到什么
SkillSpector 明确把自己定义成 defense-in-depth,而不是 sandbox。它不会执行被扫描的 Skill,也不会观察 Skill 运行后真正访问了哪些文件、启动了什么进程或把数据发到哪里。这避免了扫描过程主动触发恶意代码,但也决定了它只能判断静态内容。
项目列出的限制包括非英文内容、图片中的文字、编译或加密内容和运行时行为。源码里也有明确的扫描边界,例如跳过 .git、node_modules、虚拟环境和大部分隐藏文件。新版 inspection ledger 会记录这类排除项,让报告使用者知道哪些内容没有被检查;但记录盲区并不会消除盲区。
这种限制不是 SkillSpector 独有的实现问题。2026 年 7 月的另一篇预印本论文 《Cloak and Detonate》 对多款静态、混合和 LLM-as-judge 扫描器做了规避实验。研究者通过结构混淆改变危险代码的表面形式,或者把真实 payload 打包起来,在运行时再恢复。结果显示,依赖安装时可见内容的扫描器很容易受到语义保持型规避影响。
这项研究没有单独评估 SkillSpector 2.5.1,所以不能把论文里的绕过比例直接套到它身上。但它指出的机制边界仍然成立:安装前扫描看到的是磁盘上当时存在、并且被纳入范围的内容;Agent 运行时下载、解密或生成的下一阶段代码,不在这张快照里。
更现实的是分层防线
我更愿意把 SkillSpector 放在安装门禁这一层,而不是把它当成最终裁判。一个更完整的流程可以是:
- 核对来源、作者和仓库历史,尽量固定到审查过的 commit,而不是持续跟随可变分支。
- 安装前扫描整个 Skill 目录、脚本和依赖声明,对排除文件与
CAUTIONfindings 做人工复核。 - 给 Agent 收缩文件、终端、网络、密钥和外部服务权限,不让一个普通 Skill 自动继承所有能力。
- 对来源不明或能力较强的 Skill 使用隔离账户、容器或沙箱,避免直接接触真实凭证和生产代码。
- 在运行时观察进程、文件和网络副作用,因为这部分信息不会出现在纯静态报告里。
- Skill 更新后重新扫描和审查。通过一次检查的是某个版本,不是这个名字之后的所有内容。
SkillSpector 解决的是一个真实而必要的问题:在 Agent 信任第三方指令和代码之前,先把明显的风险挡下来。它越适合作为自动化门禁,就越需要把输出当成风险信号,而不是安全证书。更稳妥的最后一道边界仍然来自最小权限和运行隔离,因为即使扫描漏掉了恶意意图,Skill 能造成的后果也应该被限制住。