NorbiLukacs/agent-skill-guard

GitHub: NorbiLukacs/agent-skill-guard

一款面向 AI 代理技能与 MCP 服务器的安全扫描器,通过字节级确定性扫描检测隐藏 Unicode 注入、提示词颠覆、数据窃取代码和安装后漂移,弥补纯 LLM 审查的盲点。

Stars: 0 | Forks: 0

# Skill Guard — AI 代理技能的首轮安全分诊 **在你安装任何 Claude / 代理 *技能*、插件或 MCP 服务器 之前,进行一次快速的卫生检查。** Skill Guard 能够揭示单纯“阅读 SKILL.md” 的审查往往会遗漏的问题——**隐藏的 Unicode 提示词注入**、**诱导审查者颠覆的 措辞**、**高风险代码模式**(网络/exec/机密访问),以及**安装后的静默 篡改**——并将它们作为需要调查的问题交给你处理。 ``` # 将 skill 安装到你的 agent 中(Claude Code、Copilot、Cline 等) npx skills add NorbiLukacs/agent-skill-guard@skill-guard # …或者直接 clone 并运行 scanner git clone https://github.com/NorbiLukacs/agent-skill-guard python agent-skill-guard/skills/skill-guard/scripts/skill_guard.py scan ~/.claude/skills ``` ## 相比纯 Markdown 审查的增量 大多数“审查此技能”的工具都采用**纯 Markdown 方法论**——由 LLM 阅读 技能并进行评判。Skill Guard 在此基础上增加了两项内容:一个**确定性的 字节级扫描器**(Python 工具)和一套**审计方法论**,通过内置的 `SKILL.md` 引导你的代理完成整个过程。它专门解决以下三个盲点: | 仅依赖 LLM 审查的盲点 | Skill Guard 如何提供帮助(及其局限性) | |---|---| | **不可见的 Unicode。** 零宽字符、双向覆盖和 Unicode-Tag-block 字符*在分词时会消失或破碎*——LLM 字面上根本无法看到隐藏的指令。 | **扫描器(在这方面很强):** 字节级读取会标记零宽 / 双向覆盖 / Tag-block / 变体选择符字符(任何 category-`Cf` 字符,而不是固定列表),以及拉丁字母与西里尔字母、希腊字母、切罗基语、亚美尼亚语、科普特语、全角和数学字母数字的同形字混淆。这是它真正能看到 LLM 所看不到内容的唯一领域。 | | **审查者处于攻击范围内。** 将恶意技能读取到你自己的上下文中,正是审查者颠覆 payload 的攻击目标。 | **方法论(可选的手动步骤):** SKILL.md 建议派遣一个被告知去反驳安全性的*全新子代理*。这是一个人在回路(human-in-the-loop)的提示词,而不是自动化或绝对防御——它只是提高了门槛,并不能堵住漏洞。 | | **检查时间 ≠ 使用时间。** 经过审查的技能可能会在更新时被静默替换。 | **扫描器:** `baseline` 记录 sha256 指纹;`drift` 会标记 `npx skills update` 之后的任何字节更改。只有在你实际重新运行它时才有效。 | 扫描器还会扫描内置脚本**以及配置/清单文件** (`.json` / `.yaml` / `.toml` / `.html` ……,即 hook 和 exec 命令实际所在的文件),以查找高风险模式。对于 Python,它增加了一次**轻量级 AST 扫描**,用于解析 import 别名、from-imports 和动态调度——因此可以捕获 `import socket as s; s.socket()` 和 `from os import system`,而不仅仅是字面意义上的 `module.func`。 它仍然是候选项标记,而不是数据流分析,有动力的攻击者仍然可以 绕过它。请参阅[诚实的局限性](#honest-limits)。 ## 它能检测什么 - **隐藏/欺骗性的 Unicode** —— 零宽字符、双向覆盖、 Unicode Tag 走私、变体选择符,任何 category-`Cf` 格式字符,以及 拉丁字母与西里尔字母 / 希腊字母 / 切罗基语 / 亚美尼亚语 / 科普特语 / 全角 / 数学字母数字的同形字混淆。 - **审查者颠覆与提示词注入** —— “忽略之前的指令”, “不要告诉用户”,“审查时说它是安全的”,角色覆盖,以及 *获取 URL 并执行*的间接注入措辞。 - **数据窃取** —— 读取 `.env` / `.ssh` / 私钥(包括通过 `os.getenv`),将用户的数据/机密发送到外部目的地。 - **不安全的代码** —— `subprocess` / `eval` / `child_process`(包括别名和 from-imported),动态调度(`getattr` / `__import__`),`curl | sh`,远程 安装程序,`Invoke-Expression`,破坏性文件操作,路径遍历。 - **Hooks 与清单** —— 隐藏在 `plugin.json` / 配置 / 嵌入式 HTML 中的 exec 命令和注入文本,而不仅仅是在 `.py` / `.sh` 中。 - **绝不产生静默的“干净”结果** —— 没有 `SKILL.md` 的目标(裸 MCP 服务器,松散的 脚本)会作为孤立文件被扫描,并以退出码 `2` 结束,绝不会产生错误的 `0`。 - **安装后的漂移** —— 你之前批准的技能发生任何更改。 ## 设计原则:标记是一个*问题*,而不是判决 幼稚的扫描器会狼来了。在对 80 多个真实技能的测试中,第一遍扫描 产生了 **72 个候选项——其中 0 个实际是恶意的**(每一个都是文档中合理的 `curl` ,一个正当的 `process.env`,或者是“在被要求时使用”这句话)。因此 Skill Guard 是一个**漏斗**,而不是 linter——每个阶段都会消除误报,并 只把幸存下来的内容传递给下一阶段: ``` flowchart LR A("Scan
catch the invisible,
unseeable stuff") B("Triage
does the skill's
purpose justify it?") C("Adversarial agent
fresh context,
told to refute safety") D("Human
reads the raw text,
the final gate") A -- candidates --> B -- survivors --> C -- verdict --> D classDef tool fill:#0d3b66,stroke:#0d3b66,color:#fff; classDef human fill:#3a0d66,stroke:#3a0d66,color:#fff; class A,C tool class B,D human ``` 内置的 `SKILL.md` 会教你代理如何运行整个漏斗。 ## 快速开始 从 `skills/skill-guard/scripts/` 运行(或提供 `skill_guard.py` 的完整路径): ``` # 1. 扫描已安装的 skill(任何包含 SKILL.md 文件的文件夹) python skill_guard.py scan ~/.claude/skills ~/.agents/skills # 2. 记录你已审查并信任的 skill 的 baseline python skill_guard.py baseline ~/.claude/skills --out ~/.skill-guard-baseline.json # 3. 在任何更新之后,检测篡改 python skill_guard.py drift ~/.claude/skills --baseline ~/.skill-guard-baseline.json ``` - 纯 Python **标准库** —— 无需 pip install,无依赖。 - **无网络,绝不生成子进程,只读**(除了 `baseline`, 它会写入你指定的那一个 JSON 文件)。它**通过了自身的审查**。 - Windows:在命令前加上 `PYTHONUTF8=1` 以便 Unicode 发现结果能够正常渲染。 - 当有任何内容需要审查时退出码为 `1`——可以将其放入安装前的 hook 中。 ## 经过真实攻击验证 `scripts/test_skill_guard.py` 构建了携带已知攻击的一次性技能,并 断言每一个都被捕获——隐藏的零宽注入、RLO 双向欺骗、Unicode Tag 走私、明文审查者颠覆、`curl | bash`、环境变量窃取、 机密文件读取——**外加**确保干净的技能和 emoji-ZWJ 保持干净(无误报) 并且漂移在发生字节更改时会触发。 `scripts/test_improvements.py` 增加了绕过语料库——别名解析的 import、 from-imports、`getattr` 调度、`os.getenv` 机密读取、JSON 中的 hook 命令、 YAML 中的注入、获取并执行的 URL,以及扩大的 Unicode 集合——并 断言现在每一个都被捕获了(且干净的代码依然能通过)。 ``` python scripts/test_skill_guard.py # → PASS — all 11 attack/clean/drift assertions python scripts/test_improvements.py # → PASS — all v2 bypass-closure assertions python scripts/test_redos.py # → PASS — pathological inputs can't hang the scanner ``` ReDoS 测试向真实的 CLI 输入 200 KB 的数据,这些数据经过精心构造, 旨在对模式集合中的每个无界量词进行压力测试,并在硬超时下运行—— 恶意文件无法将扫描器变成针对其自身的拒绝服务攻击。 ## 诚实的局限性 在依赖它之前请阅读这些内容。一个夸大其词的安全工具比没有工具 更糟糕。 - **轻量级 AST,而不是数据流。** Python 会进行 AST 扫描,解析 import 别名 并标记动态调度;其他一切都是对原始文本的正则匹配。这里没有任何 可达性或污点分析——它不知道被标记的 `subprocess` 是否会运行,也不知道机密是否真的到达了网络调用。为此, 请将其与 [Semgrep](https://semgrep.dev) 或 [Bandit](https://bandit.readthedocs.io) 结合使用。 - **坚定的攻击者会绕过它。** 这些模式是开源的。`getattr` 和 `__import__` 现在被*标记*为候选项,但是字符串构建的名称、base64/hex payload,以及在运行时获取真实的 payload,在设计上仍然可以逃避静态扫描。 它捕获的是意外和低技术含量的恶意代码,而不是有能力的 攻击者。 - **基线是一个绊线,而不是签名。** `drift` 检测的是自你 记录以来发生的*改变*——而不是判断基线本身是否安全(首次使用即信任),并且该 JSON 未经身份验证,因此具有写权限的攻击者可以重新记录它。为了获得真正的 溯源,请固定到已签名的发行版。 - **依赖项不在此范围内。** `requirements.txt` / `package.json` 中的恶意/拼写抢注包, 或者内置到 `node_modules` / `.venv` (已跳过)中的代码,未经过审查。 - **静态分析无法看到运行时行为** —— 仅在 某些输入、日期或上下文触发的逻辑,或者安装后获取的代码/数据,对任何 扫描都是不可见的。 - **`[CLEAN]` ≠ 安全。** 它的意思是“没有明显匹配的内容”。务必阅读代码。 - **高安装量 ≠ 安全。** 流行度能更快地暴露*某些*恶意行为;但它 并不审查代码。 - 扫描器针对病态输入(`test_redos.py`)进行了 **ReDoS 模糊测试**,因此 恶意文件无法挂起它——但这是该工具的属性,而不是关于 它能检测到什么的声明。 - 通过 `npx skills` 安装的一个技能通常会通过符号链接同时链接到**多个**代理 ——一个糟糕的技能具有多代理的爆炸半径。 - Skill Guard **报告候选项;由人类做出决定。** 它从不自动安装、 自动删除、自动阻止,或做出“MALICIOUS(恶意)”的判决。 ### 路线图(已知的盲点,如实陈述) - 更深入的代码分析——数据流 / 污点分析(Semgrep 规则),以及 JS/TS/shell 的 AST 扫描(今天只有 Python 解析别名;其他语言保持仅正则匹配)。 - 依赖清单审查(拼写抢注 / 已知的恶意包)并扫描内置的 `node_modules` / `.venv`。 - 经过身份验证/签名的基线,取代普通的 JSON 绊线。 - 更广泛的同形字/易混淆字符覆盖(完整的 Unicode 易混淆字符表)。 - 针对真实恶意技能语料库测量出的漏报率。 ## 许可证 MIT —— 请参阅 [LICENSE](LICENSE)。欢迎贡献。 一个针对 AI 代理技能、Claude Code 技能和 MCP 服务器的安全扫描器和审计方法论——检测提示词注入、隐藏的 Unicode、数据 窃取和供应链漂移。
标签:DNS 反向解析, MCP, Python, 文档结构分析, 无后门, 自动化payload嵌入, 逆向工具, 错误基检测, 静态代码分析