<!-- title: 造密钥扫描Skill实战开发 -->

## 概述

本教程带你从零开发一个名为 `secret-scanner` 的自定义 Claude Code Skill，它能在代码库里自动发现硬编码的密钥、API Key、令牌和私钥，并产出一份按严重度排序的修复报告。学完你将掌握 `SKILL.md` 的结构、YAML frontmatter 各字段、渐进披露（progressive disclosure）设计、捆绑脚本与模板的组织方式，以及如何让技能精准触发、安全受限地运行。适合已经用过 Claude Code、想把重复性工作固化成可复用能力的中级开发者。

## 前置条件

- 已安装 Claude Code（CLI 版本，2025 年后的版本均原生支持 Skills）
- Python 3.8+（扫描脚本用它实现；脚本也可用 Node、Go、Bash 等任意语言替代）
- 一个用于测试的示例代码目录（后面会教你造一个含"假密钥"的测试场）
- 基本的 Markdown 与正则表达式知识

---

## 一、先想清楚：一个 Skill 到底是什么

Claude Code 的 Skill 本质上是一个**文件夹**，核心文件是 `SKILL.md`。文件夹里还可以放脚本、模板、参考文档等任意资产。Claude **不会**一次性把整个技能加载进上下文，而是采用**渐进披露（progressive disclosure）**机制：

- **始终可见**的只有 `SKILL.md` frontmatter 里的 `name` 和 `description`；
- 当用户请求匹配上 `description` 时，Claude 才把 `SKILL.md` **正文**读进上下文；
- 正文里若引用了某个脚本或参考文档，Claude 才在真正需要时去读它。

这套机制决定了写 Skill 的两条铁律：

1. **`description` 要写成"何时该用"**——它是技能唯一的触发入口，写得越具体、越带场景词，触发越精准。
2. **正文要短，细节下沉到子文件**——把长规则、大段示例挪到 `rules/`、`templates/`、`scripts/`，让 Claude 按需加载，避免污染上下文。

> 与斜杠命令（slash command）的区别：斜斜命令需要用户手动输入 `/xxx` 才触发；而带 `description` 的 Skill 可以被模型**自动唤起**，也能用 `/技能名` 显式调用。本教程做的是会自动触发的 Skill。

---

## 二、规划目录结构

先在脑中画出最终形态，再动手写文件，能少走弯路。我们的 `secret-scanner` 结构如下：

```
~/.claude/skills/secret-scanner/
├── SKILL.md              # 入口：frontmatter + 精简工作流
├── scripts/
│   └── scan.py           # 扫描引擎，输出 JSON 结果
├── templates/
│   └── report.md         # 报告模板
└── rules/
    ├── patterns.md       # 完整正则规则与误报处理（按需加载）
    └── remediation.md    # 各类密钥的轮换/撤销指引（按需加载）
```

Skill 可以放在三个位置，按就近原则生效：

| 位置 | 路径 | 适用场景 |
|------|------|----------|
| 个人级 | `~/.claude/skills/<name>/` | 所有项目通用的工具型技能 |
| 项目级 | `<repo>/.claude/skills/<name>/` | 随仓库走、团队共享，与代码强相关 |
| 插件级 | 随 Plugin 分发 | 发布到团队市场，跨项目复用 |

我们把 `secret-scanner` 放**个人级**，因为它在任何项目里都用得上。

---

## 三、第一步：创建目录与 SKILL.md 的 frontmatter

创建目录并新建入口文件：

```bash
mkdir -p ~/.claude/skills/secret-scanner/{scripts,templates,rules}
```

写入 `~/.claude/skills/secret-scanner/SKILL.md` 的 frontmatter 部分：

```markdown
---
name: secret-scanner
description: Scans the codebase for hardcoded secrets, API keys, tokens and private keys, then writes a prioritized remediation report. Use when the user asks to check for leaked credentials, audit secrets/API keys, harden code before commit/push, or run a pre-commit security scan.
---
```

**字段详解：**

- `name`（必填）：技能唯一标识，只能用小写字母、数字、连字符，**最长 64 字符**，且要和文件夹名一致。
- `description`（必填，最长 1024 字符）：这是技能的"广告位 + 触发器"。写法要点：
  - 第一句说**做什么**；
  - 紧跟一句**何时该用**（`Use when...`），列举用户可能说的触发词（"扫描密钥"、"审计安全"、"提交前检查"）；
  - 用英文写更稳，因为模型匹配 description 时英文同义覆盖更广。

```mermaid
flowchart TD
    A[用户输入: '帮我查下有没有泄露的密钥'] --> B{匹配 description?}
    B -- 命中触发词 --> C[加载 SKILL.md 正文]
    B -- 未命中 --> D[不触发该技能]
    C --> E{正文引用脚本/规则?}
    E -- 是 --> F[按需读取 scripts/scan.py]
    E -- 否 --> G[直接按正文指令行动]
    F --> H[运行扫描并产出报告]
```

> **常见问题**：技能死活不自动触发？九成是 `description` 没写好。把"Use when"那句的触发词换成你实际会说的中文/英文表达，再试。

---

## 四、第二步：写精简的正文（渐进披露的核心）

frontmatter 下面就是正文。**正文不要超过约 500 字**，把"怎么做"压缩成清晰的工作流，细节丢给子文件：

```markdown
# 密钥泄露扫描

当用户请求扫描密钥 / 检查泄露凭证 / 审计安全时激活本技能。

## 工作流程
1. 运行扫描引擎：`python3 scripts/scan.py <目标目录>`（默认扫描当前目录）
2. 读取返回的 JSON 结果
3. 按严重度排序：私钥 > AWS/GitHub/Stripe 等高敏凭据 > 通用密钥
4. 用 `templates/report.md` 作为骨架，生成报告
5. 每条发现给出修复建议（迁移到环境变量 / 密钥管理服务 / 立即撤销轮换）

## 细节按需加载
- 完整正则规则与**误报处理策略**见 `rules/patterns.md`
- 各类密钥的**轮换与撤销指引**见 `rules/remediation.md`

## 输出与安全要求
- 报告写入项目根目录 `SECRET-SCAN-REPORT.md`
- **绝不**在报告里打印完整密钥明文，只显示前 6 位 + `***`
- 发现私钥或生产令牌时，在报告顶部用"🔴 紧急"标注并提醒立即撤销
```

注意最后一句"细节按需加载"——这正是渐进披露的落地：Claude 默认不读 `rules/`，只有当它需要判断"这条匹配是不是误报"时，才会去打开 `rules/patterns.md`。

---

## 五、第三步：写扫描引擎 `scripts/scan.py`

技能的价值很大程度上来自捆绑的脚本。在 `scripts/scan.py` 写入一个可独立运行的正则扫描器：

```python
#!/usr/bin/env python3
"""secret-scanner: 在代码库中扫描硬编码密钥，输出 JSON。"""
import re, sys, os, json

# (规则名, 正则, 严重度)
RULES = [
    ("Private Key Block",  r"-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----", "critical"),
    ("AWS Access Key",     r"AKIA[0-9A-Z]{16}", "high"),
    ("AWS Secret Key",     r"aws_secret_access_key\s*=\s*['\"][A-Za-z0-9/+=]{40}['\"]", "high"),
    ("GitHub Token",       r"gh[pousr]_[A-Za-z0-9]{36,}", "high"),
    ("Google API Key",     r"AIza[0-9A-Za-z_\-]{35}", "high"),
    ("Stripe Secret Key",  r"sk_live_[A-Za-z0-9]{24,}", "high"),
    ("Slack Token",        r"xox[baprs]-[A-Za-z0-9-]{10,}", "medium"),
    ("Generic Secret",     r"(?i)(password|passwd|pwd|secret|token|api[_-]?key)\s*[:=]\s*['\"][^'\"]{16,}['\"]", "medium"),
]

SKIP_DIRS = {"node_modules", ".git", "venv", ".venv", "__pycache__", "dist", "build"}
SCAN_EXT  = {".py", ".js", ".ts", ".go", ".java", ".rb", ".php",
             ".env", ".yml", ".yaml", ".json", ".sh", ".conf", ".ini", ".toml", ".tf"}

def scan(root):
    findings = []
    for dirpath, dirnames, files in os.walk(root):
        dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]   # 原地剪枝
        for f in files:
            if os.path.splitext(f)[1] not in SCAN_EXT:
                continue
            fp = os.path.join(dirpath, f)
            try:
                with open(fp, encoding="utf-8", errors="ignore") as fh:
                    for i, line in enumerate(fh, 1):
                        for name, pat, sev in RULES:
                            if re.search(pat, line):
                                findings.append({
                                    "file": fp, "line": i, "rule": name,
                                    "severity": sev,
                                    "snippet": line.strip()[:80],
                                })
            except OSError:
                pass
    findings.sort(key=lambda x: ["critical","high","medium"].index(x["severity"]))
    return findings

if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "."
    print(json.dumps(scan(target), indent=2, ensure_ascii=False))
```

赋予执行权限，确保 Claude 能直接调用：

```bash
chmod +x ~/.claude/skills/secret-scanner/scripts/scan.py
```

**这段脚本的设计要点：**

- 用 `dirnames[:] = ...` **原地修改**实现目录剪枝，避免 `os.walk` 误入 `node_modules` 造成扫描爆炸；
- 按文件后缀白名单扫描，控制扫描范围与噪声；
- 严重度分级并预排序，让报告天然按优先级呈现；
- 输出 JSON，方便 Claude 解析后再加工成人类可读报告。

---

## 六、第四步：写规则文档与报告模板（按需加载层）

`rules/patterns.md` 示例（只放 Claude 需要判断误报时才读的细节）：

```markdown
# 密钥匹配规则与误报处理

## 已知误报
- 示例代码、文档、单测 fixture 里的假密钥（如 `AKIAIOSFODNN7EXAMPLE`）应忽略
- 环境变量**引用**（`os.getenv("AWS_KEY")`）不是泄露，只有**赋值字面量**才算

## 处理策略
- 若文件名含 `test/`、`example/`、`docs/` 或后缀 `.md`，降低严重度一档
- 对 Generic Secret 规则，先确认右侧是否为占位符（`your-key-here`、`xxx`），是则跳过
```

`templates/report.md` 报告骨架：

```markdown
# 🔐 密钥泄露扫描报告

扫描时间：{{date}}　扫描目录：{{target}}
**发现 {{count}} 处疑似泄露**（🔴 紧急 {{critical}} / 🟠 高危 {{high}} / 🟡 中危 {{medium}}）

## 紧急处理清单
> 含私钥或生产令牌的项目，请立即在各服务商后台**撤销并轮换**对应凭证。

| 严重度 | 文件 | 行号 | 规则 | 片段（已脱敏） |
|--------|------|------|------|----------------|
{{rows}}

## 修复建议
- [ ] 将所有硬编码密钥迁移到环境变量或密钥管理服务（AWS Secrets Manager / Vault）
- [ ] 撤销已泄露的凭证并轮换
- [ ] 在 `.gitignore` 中加入 `.env`，并考虑接入 pre-commit 的密钥扫描钩子
```

---

## 七、第五步：造一个测试场，验证整条链路

新建测试目录，故意埋几个假密钥：

```bash
mkdir -p /tmp/demo-app && cd /tmp/demo-app
cat > config.py <<'EOF'
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
GITHUB_TOKEN   = "ghp_0123456789abcdef0123456789abcdef0123"
api_key = os.getenv("STRIPE_KEY")   # 这是引用，不应报
EOF
cat > id_rsa.bak <<'EOF'
-----BEGIN RSA PRIVATE KEY-----
fakecontent
-----END RSA PRIVATE KEY-----
EOF
```

先独立验证脚本：

```bash
python3 ~/.claude/skills/secret-scanner/scripts/scan.py /tmp/demo-app
```

**预期输出（JSON）：**

```json
[
  {
    "file": "/tmp/demo-app/id_rsa.bak",
    "line": 1,
    "rule": "Private Key Block",
    "severity": "critical",
    "snippet": "-----BEGIN RSA PRIVATE KEY-----"
  },
  {
    "file": "/tmp/demo-app/config.py",
    "line": 2,
    "rule": "GitHub Token",
    "severity": "high",
    "snippet": "GITHUB_TOKEN   = \"ghp_0123456789abcdef0123456789abcdef0123\""
  }
]
```

注意 `api_key = os.getenv(...)` 这行**不会**出现在结果里——这正是 `rules/patterns.md` 里"环境变量引用不算泄露"的预期行为（脚本层面靠正则只匹配字面量赋值实现）。

---

## 八、第六步：让 Claude Code 触发并使用这个技能

回到任意项目，启动 Claude Code，输入一句自然语言：

> 帮我扫一下当前项目里有没有泄露的密钥

预期发生的事：模型识别到"扫描密钥/泄露"等触发词，自动加载 `secret-scanner` 的正文，然后执行 `scan.py`，最后按 `templates/report.md` 写出 `SECRET-SCAN-REPORT.md`。

你也可以**显式调用**：直接输入技能名 `secret-scanner`，或用它对应的斜杠形式。若想强制只能手动触发、不允许自动唤起，在 frontmatter 加一行：

```yaml
disable-model-invocation: true
```

---

## 九、第七步：收紧权限——给技能上"最小权限"

默认情况下技能继承会话的全部工具。出于安全考虑，应限制 `secret-scanner` 只能读文件、只能跑它自己的脚本。在 frontmatter 增加：

```yaml
allowed-tools: Bash(python3 scripts/scan.py:*), Read, Write(SECRET-SCAN-REPORT.md)
```

这样该技能**不能**执行 `rm`、`git push`、联网等危险操作，即便被恶意输入诱导也无法越权。

```mermaid
sequenceDiagram
    participant U as 用户
    participant C as Claude Code
    participant S as secret-scanner 技能
    participant P as scan.py
    U->>C: "扫描项目里的密钥"
    C->>S: description 命中，加载 SKILL.md
    S->>P: 运行 python3 scripts/scan.py .
    P-->>S: 返回 JSON 发现清单
    S->>S: 按模板脱敏 + 排序
    S-->>C: 写入 SECRET-SCAN-REPORT.md
    C-->>U: 报告摘要 + 修复建议
```

---

## 十、完整实战案例回顾

把以上片段拼起来，一个**完整可运行**的技能就成型了。最终交付：

1. 一个能自动触发的 `secret-scanner` Skill；
2. 一份按严重度排序、自动脱敏的扫描报告；
3. 受限的工具权限，安全可控；
4. 可演进：往 `RULES` 里加正则、往 `rules/` 里加误报策略即可持续增强，无需改动入口。

**进阶玩法**：

- 把它**项目级**化：放进 `<repo>/.claude/skills/`，让整个团队 clone 仓库即拥有该能力；
- 接入 **hook**：在 `settings.json` 里配 `PreToolUse` 钩子，每次提交前自动跑一次扫描，命中则阻断；
- 打成 **Plugin**：连同一个 `plugin.json` 打包，发布到团队市场（参见 Plugin 打包教程）。

---

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营 | ✅ 中级开发者训练营、安全工程训练营、AI 工程化训练营 |
| 教学时长 | 约 90–120 分钟（含动手造测试场与联调） |
| 学员产出 | 每人一个可运行的 `secret-scanner` 技能 + 扫描报告 |
| 前置衔接 | 建议先上"读懂智能体循环"或"四层配置装通 Claude Code" |
| 后续延伸 | 可衔接 Plugin 打包、Hook 自动化、MCP 服务器开发等进阶主题 |

---

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — Anthropic 官方 Skills 文档，权威说明 SKILL.md 结构、frontmatter 与渐进披露机制。
2. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — 逐字段讲解 `name`、`description`、`allowed-tools`、`disable-model-invocation` 等 15 个 frontmatter 字段。
3. [How to Build Claude Code Skills: 5 Examples (2026)](https://fp8.co/articles/Claude-Code-Skills-Complete-Developer-Guide) — 含 5 个完整示例，覆盖 SKILL.md 规范、安全控制与团队分发工作流。
4. [Creating custom skills — Claude.ai Documentation](https://claude.com/docs/skills/how-to) — 官方关于向技能目录添加 resources、scripts、reference 文档的指引。
5. [Build a Claude Skill: YAML Frontmatter, SKILL.md Instructions](https://sjramblings.io/building-skills-for-claude-part-2/) — 动手实战教程，讲解 SKILL.md 格式与正文撰写。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
