Community编程与开发github.com

jasveerai88/skill-vet — 智能体技能审查工具

在安装智能体技能包之前进行审查,检测窃取、监视、隐蔽发送等危险行为,并提供文件和行号的证据。静态只读分析,纯 Python 实现。

skill-vet — 智能体技能审查工具 是什么?

skill-vet 是一个专为 AI 智能体(如 Claude Code、Cursor 等)设计的技能包安全审查工具。它能够静态分析技能包的源代码,在安装前检测是否包含窃取凭据、监视用户行为或隐蔽发送数据等危险行为。支持 JSON 输出和严格模式,适合集成到 CI/CD 或安装工作流中。 该工具的核心规则基于三种危险模型:窃取(STEAL)、监视(SPY)和隐蔽发送(HIDDEN SENDING)。它会扫描包内所有文本文件,识别敏感操作组合(如凭据读取+网络发送),并给出确切的文件和行号证据。纯 Python 实现,不执行任何包内代码,安全可靠。 支持平台:任何可以运行 Python 的环境(CLI 工具),特别适合在 CI 管道中自动审查来自外部来源的技能包。输出包含详细的发现报告、文件清单和风险分类。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/jasveerai88/skill-vet/tree/HEAD

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

skill-vet — 智能体技能审查工具 是做什么的?

在安装智能体技能包之前,使用此命令进行审查。当任务涉及来自此机器外部的技能包时——市场下载、他人链接的仓库、客户端发送的包——都应先运行此命令,再将包安装到智能体将加载它的任何位置。

单一命令

skill-vet vet <技能包文件夹> [--json] [--strict]

(通过 pipx install skill-vet 或 pip install skill-vet 安装。从源码检出运行,同样的命令是 python3 src/skill_vet.py vet <技能包文件夹> …。)

  • 参数是包的根文件夹(包含 SKILL.md 的文件夹)。每次运行一个包。
  • 退出码:0 判定 CLEAN 或 REVIEW,1 判定 BLOCKED(使用 --strict 时 REVIEW 也返回 1),2 输入错误(文件夹不存在、不是文件夹、参数错误)。在 --json 模式下,每个错误(包括参数错误)都是一个带有 error 键的 JSON 对象,永远不会输出 traceback。
  • 只读且不执行任何代码。 Vet 将包作为静态文本读取:它从不导入、运行、shell 调用或求值包中的任何内容。其自身源码不导入任何网络或子进程模块。可以安全地指向一个你完全不信任的包。

危险模型(产品核心)

包被认为危险仅当它做了以下三件事之一——所有者的固定规则(2026-10-30),严格应用:

  1. 窃取(STEALS)——读取凭据存储、浏览器配置文件、SSH 密钥或环境机密,并将结果发送或复制出去。
  2. 监视(SPIES)——捕获屏幕、键盘、麦克风、摄像头或浏览器历史记录,并持久化或发送捕获内容,且文档未披露该捕获。
  3. 隐蔽发送(SENDS IN SECRET)——网络调用的负载或目的地被隐藏:到未公开端点的编码负载、发送到未公开端点的本地文件内容、结合包外写入的发送,或者来自声称离线工作的包的任意发送。

单一模式命中本身不构成危险判定。 每个测试需要在同一个文件中同时出现证据的组合,且发现结果会引用两个证据的文件和行号。任何不足组合的情况都会给出包含证据的警告。

同一文件中的证据判定结果
机密读取 + 发送,或机密读取 + 复制/移动到外部路径DANGER_STEALING(BLOCKED)
捕获 +(发送或持久化),捕获类型未在 SKILL.md / README.md 中披露DANGER_SPYING(BLOCKED)
发送 + 编码负载,端点未披露或非字面量DANGER_HIDDEN_SENDING(BLOCKED)
发送 + 读取包外文件,端点未披露DANGER_HIDDEN_SENDING(BLOCKED)
发送 + 写入包外,端点未披露DANGER_HIDDEN_SENDING(BLOCKED)
发送 + 文档声称离线/仅本地运行DANGER_HIDDEN_SENDING(BLOCKED)
机密读取,同一文件中无出口操作SECRET_ACCESS(WARN)
向文档从未提及的端点发送,无隐藏组合UNDISCLOSED_NETWORK(WARN)
发送时没有字面量 URL 可检查DYNAMIC_ENDPOINT(WARN)
编码负载到达已披露端点ENCODED_PAYLOAD(WARN)
捕获 + 持久化/发送,捕获类型已在文档中披露DISCLOSED_CAPTURE(WARN)
捕获,无持久化或发送CAPTURE_CAPABILITY(WARN)

公开的判断原则:

  • 窃取不需要隐蔽:将凭证发送到文档中描述的端点仍然是窃取——文档承认它并不会使其安全。披露仅区分监视和隐蔽发送与其诚实对应物。
  • 在同一个文件中已被判定为窃取出口操作一半的发送,不会再次被计为隐蔽发送。
  • 捕获“披露”按类型对照 SKILL.md / README.md 中的普通词语检查(截图、按键、麦克风、摄像头、浏览器历史等)。捕获并说明的包是可明确接受的能力,而不是间谍。
  • 出口成分按文件收集,包括所有代码行和注释(保守原则:能力证据不会因为靠近注释而被忽略)。卫生检查(shell=True、eval、下载执行)跳过完整行注释——注释中提及的模式是文档,不是代码。

规则表

规则严重级别依据
MISSING_SKILL_MDERROR包根目录缺少 SKILL.md(不同大小写的名称会产生 SKILL_MD_CASE,WARN,并使用该文件)。
FRONTMATTER_MISSINGERRORSKILL.md 没有 --- 块。
FRONTMATTER_PARSEERROR存在超出支持子集的前置信息行(见下文);引用该行。
REQUIRED_FIELDERRORname 或 description 缺失或为空。
NAME_DIR_MISMATCHWARN规范化后的前置信息名称 ≠ 文件夹名称。
NAME_FORMATWARN名称不是小写 kebab-case 格式。
BROAD_TOOL_GRANTWARNallowed-tools 条目为 *、Tool(*) 通配符或裸 Bash。像 Bash(npm run *) 这样的限定形式不会触发。
BROKEN_REFERENCEERRORSKILL.md 中引用的本地文件(markdown 链接或反引号路径)或脚本中的路径字面量不存在。解析时先尝试引用文件所在文件夹,再尝试包根目录。
REFERENCE_OUTSIDE_PACKWARN引用解析到包文件夹之外。
LARGE_FILEWARN文件超过 1 MiB(1,048,576 字节)。
BUNDLED_BINARYWARN二进制文件(已知二进制扩展名,或前 8 KiB 包含 NUL 字节)。无法静态审查二进制文件。
SYMLINK_OUTINFO符号链接的目标离开包;不扫描该目标。
DANGER_STEALINGERROR, danger上述组合表。
DANGER_SPYINGERROR, danger上述组合表。捕获类型:屏幕、键盘、音频、摄像头、历史记录(同一文件中历史存储读取加数据库读取)。
DANGER_HIDDEN_SENDINGERROR, danger上述组合表。
SECRET_ACCESSWARN凭据存储/环境机密读取,同一文件中无出口操作。机密来源:.ssh / 密钥文件、.aws、.gnupg、.netrc、存储的凭据文件、浏览器存储和配置文件、.env 文件、环境访问(命名包含 TOKEN / SECRET / PASSWORD / API_KEY / …)、整体环境读取。
UNDISCLOSED_NETWORKWARN网络调用的端点主机未出现在 SKILL.md 或 README.md 中。当主机(或其父域名)作为 URL 出现在文档中时,视为已披露。
DYNAMIC_ENDPOINTWARN文件中没有字面量 URL 的网络调用。
ENCODED_PAYLOADWARN编码器的输出到达已披露端点的发送。判断“到达”的依据:编码器调用在发送的调用段内,或由编码器赋值的变量用于该段。
DISCLOSED_CAPTUREWARN捕获 + 持久化/发送,已按类型披露。
CAPTURE_CAPABILITYWARN捕获 API,同一文件中无持久化或发送。
HYGIENE_SHELL_TRUEWARN代码行中存在 shell=True / shell: true。
HYGIENE_DYNAMIC_EXECWARN代码行中存在 eval( / exec( / os.system( / os.popen( / __import__(。
HYGIENE_DOWNLOAD_EXECWARN下载内容通过管道传给 shell(`curl …
HYGIENE_OBFUSCATIONWARN文本文件中出现 base64 块(120+ 字符,大小写混合且含数字或 +/)、十六进制块(120+ 字符)、或连续 8 个以上 \xNN 转义。每个类型每个文件一个发现。
CONCEALED_UNICODEWARN文本文件中出现不可见字符(零宽字符、单词连接符、软连字符、双向控制符、Unicode 标签字符);报告数量、首行位置和字符名称。前导 BOM 为签名而非隐藏,被忽略。
CONCEALED_BLANK_RUNWARNmarkdown 文件中出现 50 行以上连续空行,且其后有内容。
HIDDEN_COMMENTWARNmarkdown 文件中出现 120 字符以上的 HTML 注释。
WRITES_OUTSIDE_PACKWARN脚本写入绝对路径、用户路径或系统路径(/tmp,/etc,~/,$HOME,…)。
INSTALL_HOOKWARN安装时运行的文件:setup.py、install.* / setup.sh、.pth 文件,或包含 preinstall/install/postinstall 钩子的 package.json。

判定结果:BLOCKED = 至少一个危险发现 → 退出码 1。REVIEW = 无危险,但至少一个 ERROR 或 WARN → 退出码 0(使用 --strict 时为 1)。CLEAN = 无 ERROR 或 WARN(可保留 INFO)→ 退出码 0。REVIEW 故意返回 0:REVIEW 意味着“应由人类阅读”,而用于安装门控的脚本应在危险发生时失败,而不是对警告失败——--strict 专为不赞同此策略的人提供。

前置信息子集

解析器读取技能包实际使用的前置信息格式:key: value 标量(去除引号)、key: [a, b] 内联列表、key: 后跟 - item 块列表、以及折叠到前一个标量的缩进延续行。它故意不是完整的 YAML 解析器。超出子集的行会产生 FRONTMATTER_PARSE 错误并引用该行——vet 报告它无法读取的内容,而不是猜测。

扫描范围

  • 包中每个文件都被记录并获取大小。跳过 .git 和 __pycache__ 目录。
  • 文本扫描覆盖脚本文件(根据扩展名或 shebang)、markdown 文件和其他文本文件,每文件最大 2 MiB。二进制文件和包外符号链接目标被记录但不扫描。
  • 引用收集自 SKILL.md 和脚本文件。其他 markdown 文件中的引用(如 references/*.md 链接到其他文件)在 v1.0.0 中不会跟进。
  • 披露仅对照包根目录的 SKILL.md 和 README.md 进行判断——这是安装包的人预期已阅读的两个文档。

JSON 模式(--json)

一个对象:

  • tool,version,command
  • pack:dir(传入的路径),dirname,name(前置信息中的名称或 null),skill_file
  • files[]:path,bytes,kind(script|markdown|text|binary|other),binary,scanned,symlink_out
  • findings[]:rule,severity(ERROR|WARN|INFO),danger(布尔),file(或 null),line(或 null),message,fix
  • summary:findings,errors,warnings,info,danger——计数与 findings 完全一致
  • verdict(CLEAN|REVIEW|BLOCKED),verdict_rule,limits

发现结果按确定性排序:先 danger,然后严重级别、文件、行号、规则——同一包始终产生相同报告。

明确非目标(诚实说明)

  • 它从不执行任何代码。 不运行、不导入、不编译、不解析 shell;不解码负载(编码块被报告,但不打开)。
  • 静态模式是证据,不是证明。 一个在运行时用无害片段组装行为、将组合跨越两个文件(vet 按文件判断组合)、或用纯正明文编写社工内容的包可以通过。CLEAN 判定仅表示没有模式触发——README 如此声明,每个报告也如此说明。
  • 二进制文件无法触及。 捆绑的二进制文件被标记——正是因为 vet 无法对其做出任何判断。
  • 无遥测、无网络、无写入。 仅使用标准库;源码中完全不包含网络或子进程代码(测试会解析其导入并证明)。
  • 模式表是快照。 新的捕获库、新的凭据存储和新的客户端 API 不断出现;源码中的表按发布版本标记日期,不被视为完整。

相关技能