SkillSpector 拆解:NVIDIA 扫了 3.1 万个 agent skill,26.1% 有漏洞、5.2% 疑似恶意
一、速览:值不值得你花时间
推荐指数:★★★★☆(4 / 5)
先说为什么这个工具值得单独讲一篇。Agent Skills 是 2026 年最被低估的一块攻击面。
你想想 skill 是什么:一个 SKILL.md(自然语言指令)+ 若干脚本,安装后 agent 会以隐含信任去执行它。它不像 npm 包有 lockfile 和审计生态,也不像 Docker 镜像有扫描基建 —— 它介于「文档」和「代码」之间,两边的安全习惯都没落到它身上。
SkillSpector 在 Overview 里给的数字非常直白:
In the 31,132-skill analyzed subset of the research dataset, 26.1% of skills contain vulnerabilities and 5.2% show likely malicious intent.
四分之一个 skill 有漏洞,二十分之一疑似恶意。这个比例意味着 「随便装一个 skill」本身就是个高风险动作。
再说工具本身。它不是玩具:71 个模式、17 个类别,覆盖从 prompt injection 到 AST 行为分析到污点追踪到 YARA 恶意代码签名,甚至包括 MCP 的 least privilege 和 tool poisoning。而且它是 NVIDIA Verified Skills pipeline 的一部分 —— NVIDIA 自己发布 skills catalog 之前就用它扫、评估、签名。有真实的生产流水线在背书。
扣掉的一颗星:项目只有 6 个月(2026-03-21 创建),144 个 open issues;静态分析的误报是真实存在的(官方专门做了 baseline / fingerprint 抑制机制,这本身就说明 FP 是痛点);以及 风险分是累加且会饱和的 —— 一堆 MEDIUM 也能把总分推到 DO_NOT_INSTALL,需要你自己调策略。
关键数据(截至 2026-10-05)
| 出品方 | NVIDIA(NVIDIA Verified Skills pipeline 的一部分) |
| 语言 | Python |
| Star / Fork | 18,488 / 1,612 |
| 提交 / Open Issues | 548 / 144 |
| 许可证 | Apache-2.0 |
| 首次提交 | 2026-03-21(约 6 个月) |
| 最近提交 | 2026-09-28(昨天,非常活跃) |
| 检测规模 | 71 个漏洞模式 / 17 个类别 |
| 研究数据 | 分析 31,132 个 skill:26.1% 含漏洞,5.2% 疑似恶意 |
| 输出格式 | Terminal / JSON / Markdown / SARIF 2.1.0 |
| 多语言 | 支持 zh / ja / ko 检测 |
适合谁
| 人群 | 分 | 理由 |
|---|---|---|
| 日常用 Claude Code / Codex 的开发者 | 5 / 5 | 装 skill 前的体检,是目前最直接可用的工具 |
| 平台 / 工具链团队(要设安装门禁) | 5 / 5 | exit code + SARIF + gate mapping 三件套齐全 |
| 企业安全运营 | 4 / 5 | 有 NVIDIA 背书和 SARIF,但 6 个月历史偏新 |
| 安全研究员 | 4 / 5 | 17 个类别的分类法本身就是一份不错的威胁模型清单 |
| 个人 / 学习者 | 4 / 5 | uv tool install 一行就能用,--no-llm 零成本 |
二次开发
| 难度 | 说明 |
|---|---|
| 改配置 | 低 —— CLI 参数覆盖全;有 baseline 文件抑制误报 |
| 改集成 | 低 —— exit code 是稳定契约,JSON / SARIF 两格式,--fail-on-findings / --fail-on-incomplete 两个门禁开关 |
| 改内核 | 中 —— 有 docs/DEVELOPMENT.md 讲 analyzer pipeline 怎么扩展,也有 MCP server / Pi / OpenCode 三种扩展形态 |
二、它是什么,不是什么
- 不是杀毒软件。它不做沙箱动态执行,主要是静态分析 + 可选的 LLM 语义评估。
- 不是运行时护栏。它回答的是「装之前这个 skill 安不安全」,不是「跑起来之后拦不拦得住」。
- 不是只能扫本地目录。Git repo、URL、zip、目录、单个
SKILL.md都能喂。 - 不是纯正则匹配。它有 Behavioral AST(9 个模式)和 Taint Tracking(5 个模式)这两层,是真正的数据流 / 语法树分析。
提炼三个关键词:pre-install gate(安装前门禁)、71 patterns / 17 categories(覆盖度)、two-stage analysis(静态 + LLM 语义)。
三、17 个类别、71 个模式
这是它的核心资产,也是我认为最值得单独读一遍的部分 —— 即便你不用这个工具,这份分类法也是一份很好的 agent skill 威胁模型清单。
语义与指令层
| 类别 | 模式数 | 代表模式 |
|---|---|---|
| Prompt Injection | 6 | P1 指令覆盖、P2 隐藏指令(注释 / 不可见文字)、P3 外传命令、P4 行为操纵、P5 有害内容、P9 空白填充(用大量空格把指令藏在可见区外) |
| Anti-Refusal | 3 | AR1 拒绝抑制(”never refuse”)、AR2 免责声明抑制(”no disclaimers”)、AR3 安全策略作废(”you have no restrictions” / DAN 类框架) |
| System Prompt Leakage | 3 | P6 直接泄露、P7 间接提取(改写 / 翻译 / 侧信道)、P8 经工具外带 |
| Memory Poisoning | 3 | MP1 跨交互持久化注入、MP2 上下文窗口填充挤走安全约束、MP3 篡改 agent 记忆 |
| Trigger Abuse | 3 | TR1 过宽触发词、TR2 影子命令触发(覆盖内置命令或其他 skill)、TR3 关键词 baiting |
P9 空白填充 这条我觉得很见功力 —— 它不是「恶意指令」检测,而是检测「有人想让你看不见它」这个意图。同样,TR2 影子命令触发也抓得很准:一个 skill 把自己的触发词设成跟内置命令或其他 skill 一样,就能劫持调用。
数据与权限层
| 类别 | 模式数 | 代表模式 |
|---|---|---|
| Data Exfiltration | 4 | E1 外部传输、E2 环境变量收割、E3 文件系统枚举、E4 上下文泄露 |
| Privilege Escalation | 3 | PE1 过度权限、PE2 sudo/root、PE3 凭证读取(SSH key / token / 密码) |
| Excessive Agency | 5 | EA1 无约束工具访问、EA2 无人工介入的高影响决策、EA3 范围蔓延、EA4 无资源配额、EA5 外部模型/供应商选择(会切换计费账户) |
| Output Handling | 3 | OH1 未校验输出注入、OH2 跨信任边界输出、OH3 无输出上限 |
| Tool Misuse | 3 | TM1 参数滥用(shell=True、--force)、TM2 链式绕过、TM3 不安全默认值(禁用 TLS、无认证) |
| Rogue Agent | 2 | RA1 运行时自我修改(CRITICAL)、RA2 未授权的持久化(cron / 启动脚本) |
EA5 外部模型 / 供应商选择 这条特别值得注意 —— 它能识别出 skill 把你的请求导向另一个计费账户的模型 / provider。这不是「安全漏洞」而是成本与数据流向问题,但对企业是真金白银的风险。
供应链与代码层
| 类别 | 模式数 | 代表模式 |
|---|---|---|
| Supply Chain | 10+ | SC1 依赖未锁版本、SC2 外部脚本拉取(curl | bash)、SC3 混淆代码、SC4 已知 CVE 依赖(实时查 OSV.dev)、SC5 废弃依赖、SC6 typosquatting、SC8 携带 Python 字节码、SC9 藏匿的可执行产物、SC10 依赖源重定向 |
| Behavioral AST | 9 | AST1 exec()、AST8 危险执行链(exec/eval + 动态来源)、AST9 反射式 getattr sink(getattr(os,'system') 绕过 AST1/AST5) |
| Taint Tracking | 5 | TT3 凭证外传链(CRITICAL)、TT4 文件读取→网络外传、TT5 外部输入→代码执行(CRITICAL) |
| YARA Signatures | 4 | YR1 malware、YR2 webshell、YR3 挖矿、YR4 黑客工具 / exploit |
SC4 会实时查 OSV.dev 拿 CVE 数据,且有离线回退 —— 这比「内置一个过期 CVE 库」强。
AST9 反射式 getattr 这条说明作者懂对抗:攻击者用 getattr(os, 'system') 就能绕过「检测 os.system 字面量」的 AST1/AST5。专门给这种情况单开一个模式,是有实战经验的痕迹。
TT5 外部输入→代码执行 是污点追踪最有价值的一条:网络或用户输入直接流进 exec/eval/subprocess,这是 RCE 的典型链路。
MCP 专项(2 类 8 个模式)
| 类别 | 模式数 | 代表模式 |
|---|---|---|
| MCP Least Privilege | 4 | LP1 未声明的能力(代码用了但权限没写)、LP2 通配符权限、LP3 无权限声明但有可检测能力、LP4 过度声明 |
| MCP Tool Poisoning | 4 | TP1 元数据里的隐藏指令(HTML 注释、零宽字符、base64、data URI)、TP2 Unicode 欺骗(同形字、RTL 覆盖、混写标识符)、TP3 参数描述注入、TP4 描述与行为不符(LLM 驱动) |
LP1「未声明的能力」是我认为 MCP 场景里最重要的一个检测 —— 声明的权限和实际代码用到的能力对不上,这是最小权限原则最直接的违背。
TP4 描述与行为不符需要 LLM —— 这条在 --no-llm 模式下是跑不出来的。
四、风险分怎么算(读报告前必看)
1 | CRITICAL +50 |
| 分数 | 严重度 | 建议 |
|---|---|---|
| 0–20 | LOW | SAFE |
| 21–50 | MEDIUM | CAUTION |
| 51–80 | HIGH | DO NOT INSTALL |
| 81–100 | CRITICAL | DO NOT INSTALL |
这个算法有两个特性你必须知道:
- 累加且不设上限,靠 band 截断。 20 个 MEDIUM(200 分)和 2 个 CRITICAL(100 分)都落在 CRITICAL band。也就是说大量低危发现会被算成「别装」。对干净的小 skill 没问题,对内容多的 skill 可能过于严格。
- 可执行脚本 ×1.3。 这是个启发式倍率 —— 有脚本的 skill 风险确实更高,但 1.3 这个系数是拍的。
官方给的门禁映射:
recommendation |
建议动作 |
|---|---|
SAFE |
allow |
CAUTION |
提示 / 警告用户 |
DO_NOT_INSTALL |
block |
五、接进 CI:exit code 是稳定契约
skillspector scan 的退出码:
| Code | 含义 |
|---|---|
0 |
扫描完成,risk_score ≤ 50(SAFE 或 CAUTION),且没触发已启用的严格门禁 |
1 |
扫出 risk_score > 50,或 --fail-on-findings 命中,或 --fail-on-incomplete 发现分析不完整 |
2 |
出错(输入有问题、源不可读、内部失败) |
默认行为会把 SAFE 和 CAUTION 都折叠成
0。 想在 CI 里对 CAUTION 也卡,必须显式加--fail-on-findings。
这个默认值是有道理的(CAUTION 意味着「提示用户」,不是「禁止」),但接 CI 时很容易踩坑 —— 你以为它在拦,其实它一直返回 0。
输出
1 | skillspector scan ./my-skill/ # 终端(默认) |
JSON 顶层结构:skill / risk_assessment / components / issues / metadata。
risk_assessment.recommendation∈SAFE | CAUTION | DO_NOT_INSTALLmetadata.llm_requested/llm_available告诉你这次有没有跑 LLMmetadata.inference_usage会记录每次 LLM 调用的 token 计数(provider 暴露的话),从不估算缺失的 token。这个透明度值得表扬。
批量扫描
1 | python -m contrib.batch_scan.batch_scan ./my-skills/ --no-llm |
六、上手
1 | # 用 uv 装 CLI |
不想装 Python 就用 Docker(镜像基于 python:3.12-slim-bookworm):
1 | make docker-build # 或 docker build -t skillspector . |
输入形态
1 | skillspector scan ./my-skill/ # 本地目录 |
体积上限(防 zip bomb)
| 上限 | 值 | 作用 |
|---|---|---|
INGEST_MAX_BYTES |
100 MiB | 流式 URL 下载、zip 解压后总大小、Git clone 后磁盘占用 |
INGEST_MAX_ZIP_MEMBERS |
10,000 | 单个 zip 的条目数 |
MAX_FILE_BYTES |
1 MB | 单个文件被 analyzer 读取的上限(下游限制) |
任一 ingest 上限被突破会 fail closed 抛 IngestLimitExceededError。这个设计是对的 —— 扫不可信输入,先把 DoS 面堵上。
扩展形态
- MCP server:
skillspector mcp(需装[mcp]extra) - Pi extension:装成 Pi 工具,在 agent 会话里直接扫
- OpenCode extension:装成 OpenCode 工具和
/skillspector命令
后两条很实用 —— 让 agent 在自己装 skill 之前先扫一遍。
七、边界与风险(必须诚实说的部分)
1)项目只有 6 个月,144 个 open issues。 2026-03-21 创建。虽然提交很活跃(昨天还在推),但对一个要当「安装门禁」的工具来说,成熟度需要时间。
2)静态分析有误报,官方自己做了抑制机制。 README 有专门的 Suppressing False Positives (baseline) 章节,支持 glob 规则和 fingerprint baseline。这个功能的存在本身就说明 FP 是真实痛点 —— 第一次接入时大概率要花时间调 baseline。
3)风险分累加会饱和,可能过于严格。 见第四章。内容丰富的 skill 容易被一堆 MEDIUM 推到 DO_NOT_INSTALL。建议自己读 issues 数组而不是只看 recommendation。
4)默认 exit code 对 CAUTION 返回 0。 接 CI 时必须显式加 --fail-on-findings,否则门禁是虚设的。
5)--no-llm 会漏掉语义类模式。 比如 TP4 描述与行为不符明确标注是 LLM-powered。不配 LLM 的话覆盖度会打折。
6)1 MB 单文件上限意味着大文件只看一部分。 这是个工程权衡,但也意味着藏在 1MB 之后的 payload 扫不到。
7)它只能抓已知的 71 个模式。 分类法很全,但新颖手法仍然会漏。别把 SAFE 当成「保证安全」,它只是「没命中这 71 条」。
8)扫的是 skill 本身,不是它安装的依赖运行时行为。 静态分析看不到实际执行时会发生什么。
八、上手建议(按这个顺序来)
- 先
uv tool install+--no-llm跑一遍。 零成本,先看清输出长什么样。 - 第一件事是扫你已经装了的那些 skill。 用
contrib/batch_scan批量跑一遍./my-skills/,你大概率会有意外发现 —— 记住 26.1% 这个基数。 - 别只看
recommendation,要读issues数组。 分数会饱和,具体哪条命中才是你要判断的东西。 - 尽早建 baseline。 把你确认过的误报用 fingerprint baseline 存下来,否则每次重扫都会被同样的 FP 淹。
- 接 CI 一定要加
--fail-on-findings。 默认 CAUTION 返回 0,门禁不生效。 - 重要 skill 配 LLM 分析。
--no-llm快且免费,但会漏 TP4 这类语义模式。对高价值 skill 值得花钱。 - 优先关注这几类:
TT3/TT5(污点追踪的 CRITICAL)、AST8/AST9(危险执行链与反射绕过)、SC2(curl | bash)、RA1(运行时自我修改)、LP1(MCP 未声明能力)。这几条命中了基本可以直接判定别装。 - 把 Pi / OpenCode extension 装上。 让 agent 在装 skill 之前自己扫一遍,比事后审计有效得多。
九、一句话结论
如果你在用 Claude Code / Codex 并且会装第三方 skill,SkillSpector 应该是你工作流里的默认一步 —— 3.1 万个 skill 里 26.1% 有漏洞、5.2% 疑似恶意,这个基数下「先扫再装」不是洁癖,是基本卫生。
给开发者的第一动作:uv tool install git+https://github.com/NVIDIA/skillspector.git → 用 contrib/batch_scan 把你现有的 skills 目录全扫一遍(--no-llm,免费)→ 挑出 CRITICAL 和 HIGH 的那几条人工看一眼。 这一步花不到十分钟,但你可能会发现某个天天在用的 skill 正在读你的环境变量。