junjunup/skillops-forge
GitHub: junjunup/skillops-forge
面向 AI Agent 技能包的离线静态审计 CLI 工具,通过明文模式扫描检测安全风险并验证结构合规性。
Stars: 0 | Forks: 0
# SkillOps Forge
**English** · [中文](README_CN.md)
[](https://github.com/junjunup/skillops-forge/actions/workflows/ci.yml)
[](https://github.com/junjunup/skillops-forge/actions/workflows/skillops.yml)
[]()
[](LICENSE)
[]()
## 为什么还要开发一个新工具?
技能生态系统已经有了结构化 linter 和质量评分器。但没有一个工具将 **AI 时代的攻击面** —— agent 记忆窃取、身份文件读取、prompt 注入关键词、隐藏的零宽 payload —— 视为一等检测对象。
| 工具 | 形式 | 重点 | 安全规则 | 离线 | LLM 时代检查 |
| --- | --- | --- | --- | --- | --- |
| `skilllint` | CLI | 结构化 lint,跨平台 | 部分(模式级别) | ✓ | ✗ |
| `skill-tester` | CLI | AST + 示例执行 + 质量评分 | ✗ | ✓ | ✗ |
| `skillcheck` | CLI | 仅 frontmatter 验证器 | ✗ | ✓ | ✗ |
| `claude-skill-check` | GH Action | skill 主体中的密钥 | 7 种密钥模式 | ✓ | ✗ |
| `kevinsong0/skills-vetter` | Prompt skill | LLM 驱动的审查 | 定性分析 | ✗(需要 LLM) | 部分 |
| **`skillops-forge`** | **CLI + GH Action** | **lint + 明文模式安全提示 + 评分 + 报告** | **19 条明文规则** | **✓** | **✓** |
SkillOps Forge 为 *agent 私有* 文件(
`MEMORY.md`, `USER.md`, `SOUL.md`, `IDENTITY.md`, `~/.workbuddy/memory`)
提供了专门的规则,
并结合了审查器、确定性评分器、CRITICAL 一票否决策略,
以及独立的 HTML/Markdown/JSON 报告。
## 局限性
SkillOps Forge 是一个**明文模式的静态 linter**,而不是一个完整的
安全解决方案。我们已经在对抗性条件下进行了测试——以下是它
能做什么和不能做什么的通俗说明:
### 它的强项
- 当攻击者以标准的、未混淆的形式编写时,能捕捉到 19 种已记录模式的 **明文出现**(`curl | sh`、
`sudo`, `eval(`, `~/.ssh/id_rsa`, 对 `MEMORY.md` 的引用等)。
- 发现 skill pack 中的 **结构和命名问题**(frontmatter
schema,kebab-case 命名,token 预算,缺失的触发短语)。
- 生成 **确定性的、机器可读的报告**,这些报告足够稳定,
可用作针对诚实作者的 PR 阻断 CI 门禁。
### 它做不到的事 —— 我们验证过的绕过场景
以下技术**可以**在今天绕过 SkillOps Forge:
| 绕过方式 | 漏网之鱼 |
| --- | --- |
| Unicode 易混淆字符(带有西里尔字母 `ѕ` U+0455 的 `ѕudo`) | SEC-006 漏报;只有 SEC-005 可能捕捉到末尾的 `rm` |
| 使用 `bash -c "$(curl …)"` 代替 `curl … \| sh` | SEC-001 漏报;只有 SEC-010 中危域名提示会触发 |
| `curl -o /tmp/x && sh /tmp/x`(先下载后执行) | SEC-001 漏报 |
| `python -c "exec(urlopen(...).read())"` | SEC-001 漏报 |
| `__import__("builtins").exec(payload)` | SEC-014 漏报;**SEC-018 能捕捉到** |
| `getattr(__builtins__, 'ev'+'al')(payload)` | SEC-014 漏报;**SEC-018 + SEC-019 能捕捉到** |
| 分割在两个字符串中的 Base64 编码 payload | SEC-008 / SEC-013 会漏报,除非其中一半本身足够长 |
根本限制在于:**基于正则表达式的明文扫描无法理解意图。**
意志坚定的攻击者总能找到一种混淆层来击败有限的模式集。
SEC-018 和 SEC-019(在 0.2.1 版本中添加)封堵了最常见的两种反射 / 字符串拼接绕过方式,但这列表并不详尽,也永远不可能详尽。
### 建议的姿态
- 将此工具作为**众多信号中的一个**,而不是唯一的安全门禁。
将其与代码审查、运行时沙箱及源信任评估结合使用。
- 将 **SkillOps Forge 的干净运行** 视为“明文中没有明显的危险信号”,
而不是“此技能是安全的”。
- 将 **失败运行** 视为“安装前需要进行审查”,而不是恶意的证据
—— 误报是存在的,并且已被记录在案。
- 对于高风险目标(处理凭证、资金、系统配置的技能),
即使运行通过,也不要跳过人工审查。
### 不在范围内
- AGENTS.md / Codex `agents.json` 文件 —— 不予解析。
- Python AST / 运行时示例执行 —— 未实现(红线:
没有 `subprocess`,没有 LLM,没有不受信任的代码执行)。
- 对技能主体中域名的网络信誉查询 —— 完全离线。
- 对技能包的加密签名验证 —— 未实现。
我们将在未来的版本中不断扩大绕过覆盖范围,并在 `CHANGELOG.md` 中明确列出每一项新防御及每一个已知的限制。
## 快速开始(30秒)
```
# 从源码安装(直到 PyPI 发布)
pip install -e ".[dev]"
# 验证安装
skillops --help # 5 commands: scan / init-ci / version / rules / rule
skillops version # skillops-forge 0.2.1
# 扫描 skill(或整个 skill repo)
skillops scan ./my-skill --report all
# 引导一个 CI workflow,如果分数降至 70 以下则使 PR 失败
skillops init-ci --github-actions
```
报告默认存放在 `./reports/` 目录下:
| 文件 | 用途 |
| --- | --- |
| `reports/skillops-report.html` | 独立的 HTML(可直接放入 README,或作为 artifact 分享) |
| `reports/skillops-report.md` | Markdown 摘要(适合作为 PR 评论) |
| `reports/skillops-result.json` | 机器可读,schema 稳定(CI artifact) |
## CLI
```
skillops scan PATH [--report md|html|json|all] [--out-dir DIR]
[--threshold 70] [--no-cursor-rules] [--no-runner] [-v]
skillops init-ci [--github-actions / --no-github-actions]
[--out FILE] [--force]
skillops version
```
退出码:
| 代码 | 含义 |
| --- | --- |
| 0 | 通过(评分 ≥ 阈值且零 CRITICAL 发现) |
| 1 | 审计失败(低于阈值或存在任何 CRITICAL 发现) |
| 2 | 用户错误(路径错误,参数错误) |
| 3 | 内部错误(罕见;格式错误的 YAML 现在会降级为一条发现) |
## 检查内容
### 19 条安全规则 (SEC-001 → SEC-019)
| ID | 严重性 | 检测内容 |
| --- | --- | --- |
| SEC-001 | critical | 通过管道传递给 shell 的远程脚本 (`curl … \| sh`) |
| SEC-002 | high | 下载后执行 (`wget -O … && bash`) |
| SEC-003 | critical | 敏感凭证文件路径 (`~/.ssh`, `~/.aws`, `id_rsa`, `.netrc`) |
| SEC-004 | high | 隐式凭证环境变量读取 (`AWS_*`, `OPENAI_API_KEY`, `GITHUB_TOKEN`) |
| SEC-005 | critical | 破坏性 shell 命令 (`rm -rf /`, `dd if=`, `mkfs`, fork bomb) |
| SEC-006 | high | 提权 (`sudo`, `chmod 777`, `chown -R root`) |
| SEC-007 | high | 隐藏的零宽字符 (U+200B/200C/200D/FEFF) |
| SEC-008 | medium | 长 base64 / 高熵 blob(启发式检测) |
| SEC-009 | high | Prompt 注入关键词 (`ignore previous instructions`, `jailbreak`) |
| SEC-010 | medium | 向非白名单域名发送数据 |
| SEC-011 | high | 通过未经清理的变量进行 shell 注入 |
| **SEC-012** | **critical** | **Agent 身份 / 记忆文件访问** (`MEMORY.md`, `USER.md`, `SOUL.md`, `IDENTITY.md`, `CLAUDE.md`, `~/.workbuddy/memory`) |
| SEC-013 | high | Base64 / 十六进制解码动作 (`base64 -d`, `atob(`, `fromCharCode`) |
| SEC-014 | high | 动态执行 (`eval(`, `exec(`, `Function(...)`) |
| SEC-015 | high | 对原始 IPv4 地址的网络调用 |
| SEC-016 | critical | 浏览器 cookie / 登录数据 / 保存的凭证访问 |
| SEC-017 | high | 写入系统 / 特权路径 (`/etc`, `/usr`, `C:\Windows`) |
| SEC-018 | high | 反射式动态执行 (`getattr(__builtins__, ...)`, `__import__("builtins").exec`) |
| SEC-019 | high | 字符串拼接的 `eval` / `exec` / `compile` 名称 (例如 `'ev'+'al'`) |
### 结构审计 (auditor)
`frontmatter`(必填 + 推荐字段),`description`(长度 + 触发措辞),`permissions`(声明的 `allowed-tools` 与检测到的 shell 使用情况的对比),`io_schema`(输入 / 输出部分),`examples`(≥1 个代码块,可运行)。
### 运行器
示例是被*解释的,而不是被执行的*。运行器使用 `shlex` 和严格的
允许 / 拒绝列表,并且测试套件断言 `subprocess.run`, `Popen`,
`check_call` 和 `check_output` 从未被调用。
## 报告
每份报告都包含(自 0.1.2 起):
- **评分 / 风险 / 阈值 / 结果** —— 当评分 ≥ 阈值但存在 HIGH 发现时,具有一个 `⚠️ PASSED WITH CAUTION` 的中间状态。
- **建议操作** —— 基于风险等级映射的指导(例如 CRITICAL →
*“DO NOT INSTALL. Address all critical findings first.”*)。
- **权限摘要** —— 从技能主体和示例中自动提取的 *读取文件 / 写入文件 /
命令 / 网络*。
- **清单 / 发现 / 示例预演 / 合规清单**。
## 评分
```
score = max(0, 100 - Σ(weight × count))
weights: critical=25, high=12, medium=5, low=2, info=0
```
无论评分如何,单个 CRITICAL 发现都会将 `is_passed` 设为 `false`(一票否决)。
CRITICAL 否决同时适用于 `audit_findings` 和
`security_findings`;`is_passed` 是一个 Pydantic v2 `@computed_field`,因此 JSON、
Markdown 和 HTML 报告会自动保持同步。
## 一行代码搞定 CI
```
skillops init-ci --github-actions
```
生成 `.github/workflows/skillops.yml`,其中包含**锁定版本**的
`actions/checkout@v4` 和 `actions/setup-python@v5`,一个 artifact 上传步骤,以及一个
`fail-under` 阈值(默认为 `70`)。默认策略拒绝覆盖现有的 workflow;请传递 `--force` 来替换它。
## 真实案例
在一台开发机器上安装的 37 个技能上运行 SkillOps Forge
(`~/.workbuddy/skills/`),发现了两个 **真阳性** CRITICAL 发现:
| 技能 | 发现 | 证据 |
| --- | --- | --- |
| `proactive-agent` | SEC-012 × 2 | `Read SOUL.md` / `Read USER.md` (第 499–500 行) |
| `humanizer` | AUD-000 (CRITICAL) | 未加引号的多行 YAML 描述(解析器会优雅降级,而不是崩溃) |
完整分布:2 个 critical · 1 个 high · 3 个 medium · 9 个 low · 22 个 info。
请参阅 `CHANGELOG.md`(条目 `[0.1.2]`, `[0.1.4]`, `[0.2.0]`, `[0.2.1]`)以获取
逐条规则的依据以及对 skilllint 和
skillcheck 的现有技术致敬。
## 设计红线
1. **永不使用 `subprocess`** —— 运行器没有导入任何 `subprocess`;测试
使用 monkey-patch 并断言未被调用。
2. **完全离线** —— 没有任何网络调用(甚至不调用 GitHub API);`init-ci`
只写入模板文件。
3. **永不上传用户内容** —— 分析的每一个字节都保留在本地。
4. **永不执行危险命令** —— 示例通过 `shlex`
以及允许 / 拒绝列表进行解释;`curl … | sh` 会被拦截。
5. **风险辅助,而非认证** —— 每份报告中都有明确的免责声明。
## 项目结构
```
skillops-forge/
├── src/skillops_forge/
│ ├── parser/ # SKILL.md / CLAUDE.md / .cursor/rules
│ ├── auditor/ # frontmatter / description / permissions / io / examples
│ ├── scanner/ # rule loader + dedup engine
│ ├── runner/ # shlex-based dry-run, never subprocess
│ ├── reporter/ # md / html / json + scoring
│ ├── pipeline.py # parser → audit → scan → run → score → report
│ ├── plugins/ # PluginProtocol (P1: LLM judge, cross-format export)
│ ├── rules/ # YAML data-driven SEC rules
│ ├── templates/ # Jinja2 (HTML/MD reports + GH Actions yaml)
│ └── ci/ # init-ci generator
├── tests/ # 206 tests, 91% line coverage (scanner ≥95%)
├── docs/ # architecture, rules, JSON schema, mermaid diagrams
└── pyproject.toml
```
## License
[](LICENSE) · 中文版见 [README_CN.md](README_CN.md).
标签:代码质量检查, 逆向工具, 错误基检测, 静态代码分析