<!-- title: 造硬编码密钥扫描Skill -->

# Claude Code Skills 开发实战：从零造一个硬编码密钥扫描 Skill

## 概述

本教程带你从零构建一个完整可用的 Claude Code **Skill（技能）**——一个能自动嗅探源码里硬编码密钥（AWS Key、GitHub Token、私钥、Slack Token 等）的「源码密钥嗅探器」。你将掌握 Skill 的目录结构、YAML frontmatter 字段、description 触发词写法、脚本渐进披露、动态上下文注入、`allowed-tools`/`paths` 权限控制，以及用 `skill-creator` 做评测打磨触发的完整闭环。

**适用人群**：已用过 Claude Code 基本对话、写过简单脚本的工程师；想把自己重复粘贴的检查清单固化成「可复用、可触发、可分发」能力的开发者。

## 前置条件

- Claude Code 已安装（建议 v2.1.196 及以上，以支持 `${CLAUDE_PROJECT_DIR}` 等较新特性）
- 本机有 Python 3（脚本 Skill 用，也可换成 Node/Bash）
- 一个用于测试的 git 仓库（任意项目均可，没有就 `git init` 一个空目录）
- 了解 YAML 基本语法

## Skill 到底解决了什么问题

当你反复把同一段「检查清单」或「多步骤流程」粘贴到对话里时，就该把它做成 Skill。Skill 和你熟悉的三种配置有清晰分工：

| 载体 | 加载时机 | 适合放什么 |
|---|---|---|
| `CLAUDE.md` | 每次会话始终在上下文 | 项目事实、约定、不变的偏好 |
| **Skill** | **相关时才加载全文** | **可触发的过程、检查清单、带脚本的流程** |
| 自定义命令 `.claude/commands/` | 你手动 `/` 调用 | 已被 Skill 取代，二者等价但 Skill 更强 |
| MCP 服务器 | 始终注册为工具 | 需要长连接外部 API 的工具 |

关键区别：Skill 的**元数据**（name + description）在启动时预载，**全文**只在被触发时才进入上下文，**脚本**更是只看输出不看源码。这意味着你可以塞进一大堆「平时用不上、用时必须有」的规则，平时几乎零成本。

## 三层渐进披露架构

Skill 的精妙之处在于「按需加载」的三层结构。理解它，你就理解了 Skill 的组织哲学：

```mermaid
flowchart TD
    A[会话启动] --> B[预载全部 Skill 的<br/>name + description 进系统提示]
    B --> C{用户请求是否<br/>命中某个 description?}
    C -->|是| D[加载该 Skill 的 SKILL.md 全文]
    C -->|否| E[全文从不加载 零成本]
    D --> F{SKILL.md 是否引用<br/>更细的文件/脚本?}
    F -->|是| G[按需读取 reference.md<br/>或执行 scripts/xxx.py]
    F -->|否| H[直接按指令执行]
    G --> I[脚本只返回输出<br/>源码不进上下文]
    H --> I
    I --> J[产出结果]
```

记住一个原则：**SKILL.md 是目录，不是百科全书**。把详细规则库、长文档放进 `reference.md`，把可执行逻辑放进 `scripts/`，主文件控制在 500 行以内。

## Step 1：规划你的 Skill

动手前先回答三个问题，这决定了 frontmatter 怎么写：

1. **它做什么？** —— 扫描源码，揪出硬编码的密钥/凭据并按严重度分级。
2. **什么时候该触发？** —— 用户问「有没有泄露密钥」「检查一下凭据」「commit 前扫一遍安全」「这段代码安全吗」时。
3. **谁来触发？** —— 既希望用户手动 `/scan-secrets`，也允许 Claude 在谈安全审查时自动调用，但绝不能让它自动 `git push`。

## Step 2：建目录、写 frontmatter

Skill 的命令名直接来自目录名（而非 frontmatter 的 `name`）。先建目录：

```bash
mkdir -p ~/.claude/skills/scan-secrets
```

放在 `~/.claude/skills/` 是**个人级**，所有项目可用；只想给某个项目用就放该项目的 `.claude/skills/scan-secrets/`。

接着写 `SKILL.md`。frontmatter 最关键的是 `description`——它是 Claude 从上百个 Skill 里「选中你」的唯一依据。

```yaml
---
name: scan-secrets
description: Scans source files for hardcoded secrets and credentials (AWS keys, GitHub/Slack/Stripe tokens, private keys, JWTs) and ranks findings by severity. Use when the user asks to check for leaked secrets, review security before a commit, audit credentials in the codebase, or asks "is there a key/token/password hardcoded here?".
when_to_use: Commit-time security checks, PR reviews, onboarding a new repo, investigating "did I leak my token". Triggers on phrases like 密钥泄露、凭据扫描、检查 token、提交前安全检查、硬编码密钥.
argument-hint: [path]
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/scan_secrets.py *) Bash(git diff --name-only *) Bash(git status --short *) Read
paths: "**/*.py, **/*.js, **/*.ts, **/*.go, **/*.java, **/*.{yml,yaml,json,env,conf,ini,toml}, **/.*rc, Dockerfile*, .env*"
---
```

**description 写作四铁律**（来自官方最佳实践）：

1. **一律第三人称**。它是被注入系统提示的元数据，混入「我/你」会破坏发现机制。✅「Scans source files…」 ❌「I can help you scan…」
2. **「做什么 + 什么时候用」缺一不可**。前半句说明能力，后半句给出触发场景和关键词。
3. **塞入真实关键词**。用户会怎么问就把那个词写进去（token、凭据、私钥、commit 前检查）。
4. **长度要克制**。description 上限约 1024 字符，与 `when_to_use` 合并后在 Skill 列表里截断到 1536 字符。别写小作文。

其余字段说明：
- `paths` 限定只在编辑匹配文件时才自动激活，避免你写文档时也跳出来。
- `allowed-tools` 预授权扫描脚本和只读 git 命令，免去每次确认。
- `${CLAUDE_SKILL_DIR}` 解析为 Skill 自身目录，无论当前工作目录在哪都能定位到自带脚本。

## Step 3：写主体指令 + 动态上下文注入

frontmatter 下方是 Markdown 正文，即「Skill 被触发后 Claude 读到的操作手册」。把动态数据用 `!`command`` 在加载时**预先注入**，Claude 看到的是真实数据而非命令：

````markdown
## 当前改动（commit 前检查模式）
如果当前目录是 git 仓库，先用以下未提交文件清单聚焦扫描：
!`git diff --name-only HEAD 2>/dev/null; git status --short 2>/dev/null`

## 扫描任务
对 `$ARGUMENTS`（未传参则用 `.`）指定的路径运行嗅探脚本，输出按严重度分级的发现：

```bash
python3 ${CLAUDE_SKILL_DIR}/scripts/scan_secrets.py "$ARGUMENTS"
```

## 输出规范
1. 先按 CRITICAL / HIGH / MEDIUM / LOW 汇总数：例如「CRITICAL 2，HIGH 1」。
2. 逐条列出：`文件:行号 [规则] 命中片段（脱敏，只显示首尾4位）`。
3. CRITICAL（私钥、生产 Stripe/AWS 等）必须给出「立刻做什么」的一句行动建议。
4. 若脚本报告 0 命中，明确说「未发现已知模式的硬编码密钥」，并提示三条易漏点（.env 漏提交、构建产物里的 source map、CI 日志里的 token）。
5. 绝不把完整密钥明文回显到对话；脱敏后展示。

## 规则细节
完整规则库与误报排除策略见 [rules.md](rules.md)，按需阅读，不必每次加载。
````

这里有两个高价值特性：

- **动态上下文注入** `!`git diff --name-only``：在 Skill 内容送进模型**之前**执行，输出替换占位符。Claude 拿到的是真实的文件清单，命中率更高。
- **`$ARGUMENTS`**：`/scan-secrets src/auth` 中的 `src/auth` 会替换 `$ARGUMENTS`；不写占位符时则追加为 `ARGUMENTS: ...`。

## Step 4：抽离扫描脚本（渐进披露第二层）

把确定性逻辑交给脚本，而不是让模型「现场推理正则」。这样既准确（正则是死的，模型可能漏匹配），又省 token（脚本源码不进上下文，只看输出）。在 `~/.claude/skills/scan-secrets/scripts/scan_secrets.py` 写：

```python
#!/usr/bin/env python3
# Hardcoded secret scanner. Deterministic regex rules + severity ranking.
import os, re, sys, json, math

RULES = [
    # (name, regex, severity)
    ("AWS Access Key ID",  r"AKIA[0-9A-Z]{16}", "CRITICAL"),
    ("AWS Secret Key",     r"(?i)aws.{0,20}?(?:secret|priv).{0,20}?[\"\']([0-9a-zA-Z/+]{40})[\"\']", "CRITICAL"),
    ("GitHub Token",       r"gh[pousr]_[0-9A-Za-z]{36}|github_pat_[0-9A-Za-z_]{82}", "CRITICAL"),
    ("Google API Key",     r"AIza[0-9A-Za-z\-_]{35}", "HIGH"),
    ("Slack Token",        r"xox[baprs]-[0-9A-Za-z-]{10,}", "HIGH"),
    ("Stripe Live Key",    r"sk_live_[0-9a-zA-Z]{24}|rk_live_[0-9a-zA-Z]{24}", "CRITICAL"),
    ("GitLab PAT",         r"glpat-[0-9a-zA-Z\-_]{20}", "HIGH"),
    ("Private Key Block",  r"-----BEGIN (?:RSA|EC|OPENSSH|PGP|DSA) PRIVATE KEY-----", "CRITICAL"),
    ("JWT",                r"eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}", "MEDIUM"),
    ("Generic Assignment", r"(?i)(?:password|passwd|pwd|secret|token|api_key)\s*[:=]\s*[\"\']([^\"\'\s]{12,})[\"\']", "MEDIUM"),
]
SKIP_DIRS = {".git", "node_modules", "vendor", "dist", "build", ".venv", "__pycache__"}
SKIP_EXT = {".png", ".jpg", ".jpeg", ".gif", ".lock", ".min.js", ".map"}

def entropy(s):
    if not s: return 0.0
    return -sum((s.count(c)/len(s))*math.log2(s.count(c)/len(s)) for c in set(s))

def scan(path):
    hits = []
    for root, dirs, files in os.walk(path or "."):
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for fn in files:
            ext = os.path.splitext(fn)[1]
            if ext in SKIP_EXT: continue
            fp = os.path.join(root, fn)
            try:
                with open(fp, "r", encoding="utf-8", errors="ignore") as f:
                    for i, line in enumerate(f, 1):
                        for name, pat, sev in RULES:
                            m = re.search(pat, line)
                            if m:
                                secret = m.group(1) if m.lastindex else m.group(0)
                                # Generic 规则额外要求高熵，过滤 password="changeme" 之类
                                if name == "Generic Assignment" and entropy(secret) < 3.0:
                                    continue
                                masked = secret[:4] + "…" + secret[-4:] if len(secret) > 8 else "***"
                                hits.append({"file": fp, "line": i, "rule": name, "severity": sev, "match": masked})
            except (OSError, UnicodeDecodeError):
                continue
    return hits

if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "."
    hits = scan(target)
    order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
    hits.sort(key=lambda h: (order[h["severity"]], h["file"], h["line"]))
    print(json.dumps(hits, ensure_ascii=False, indent=2))
    sys.exit(1 if hits else 0)
```

要点：

- **确定性优先**：每条规则带严重度，输出 JSON 让 Claude 只负责「解读 + 给建议」。
- **熵值过滤**：通用赋值规则用香农熵剔除 `password="test"` 这类低熵误报。
- **退出码**：命中即返回非零，方便后续在 CI/钩子里做「失败拦截」。

## Step 5：支撑文件 rules.md（渐进披露第三层）

主文件里只放「读 `rules.md` 获取完整规则」的引用，长内容留在 `~/.claude/skills/scan-secrets/rules.md`。示例片段：

```markdown
# 嗅探规则与误报排除

## 严重度定义
- CRITICAL：可直接利用的生产凭据（私钥、AWS、Stripe live、GitHub PAT）
- HIGH：云厂商可变现 key（Google、Slack、GitLab）
- MEDIUM：疑似密钥需人工确认（JWT、通用赋值）

## 常见误报排除
- 测试夹具里的假密钥：路径含 `/test/` 或 `/fixtures/` 且值形如 `test_` / `example_`
- 文档示例：文件名含 `.md` 且整行以 `>` 或 `//` 开头
- 占位符：`YOUR_API_KEY` / `<token>` / `xxx`

## 易漏盲区（扫描后必提醒）
1. 已提交进 git 历史的密钥——需用 `git log -p | scan` 二次扫描
2. 构建产物 source map 里泄露的源码
3. CI/CD 日志、Docker image 层、error 上报平台里打印的 token
```

支撑文件**只挂一层**：从 `SKILL.md` 直接链接到 `rules.md`，不要再 `rules.md` 链 `advanced.md` 链 `deep.md`。引用层级越深，模型越可能漏读。

## Step 6：控制「谁能触发」与权限

你已经通过 frontmatter 完成了两件事：`paths` 限定激活时机、`allowed-tools` 预授权工具。再理解两个对立开关，覆盖绝大多数编排需求：

| 配置 | 效果 | 适用场景 |
|---|---|---|
| 默认 | 用户可 `/x`，Claude 也可自动调用 | 你的嗅探器：默认 |
| `disable-model-invocation: true` | 仅用户手动，Claude 不自动触发 | `/deploy`、`/commit` 这类有副作用的 |
| `user-invocable: false` | 仅 Claude 自动，用户菜单看不到 | 纯背景知识（如遗留系统说明） |

本 Skill 故意保持默认——既支持 `/scan-secrets` 手动，也能在用户聊「提交前帮我看看安不安全」时被自动选中。

## Step 7：测试 Skill 的三种姿势

改完文件**无需重启**——Claude Code 对 Skill 目录有实时变更检测，保存即生效（仅 `SKILL.md` 文本层面；若 Skill 同时是 plugin 改了 hooks 需 `/reload-plugins`）。

进入一个 git 仓库启动 `claude`，分别验证：

1. **自动触发**：问「提交前帮我检查一下有没有泄露密钥」，应自动加载并运行扫描。
2. **手动调用**：输入 `/scan-secrets src`，按 `argument-hint` 提示传路径。
3. **空结果路径**：在干净仓库运行，确认输出「未发现…」并附三条盲区提醒。

## Step 8：用 skill-creator 做评测打磨触发

`description` 决定生死，但靠「感觉」调很玄学。官方 `skill-creator` 自带 eval 流程：你给一组「用户提问 → 期望是否触发该 Skill」的样例，它会跑出**触发准确率与方差**，并建议改写 description。

```
/skill-creator
> 帮我为 scan-secrets 设计 eval 样例集，正例（应触发）如「这个 PR 安全吗」「会不会有 key 泄露」，
  负例（不该触发）如「帮我写个正则」「这段逻辑有 bug 吗」，然后跑一轮评测。
```

评测会告诉你两类典型病：**不触发**（description 关键词没对上用户说法）或**过度触发**（description 太宽，抢了别的 Skill 的活）。据此增删 `when_to_use` 里的触发短语，再跑，直到准确率与方差都满意。

## 完整实战案例

把所有片段拼起来，目录长这样：

```
~/.claude/skills/scan-secrets/
├── SKILL.md              # frontmatter + 主体指令（动态注入 git diff + 调脚本）
├── rules.md              # 规则库与误报排除（按需读）
└── scripts/
    └── scan_secrets.py   # 确定性扫描脚本（只看输出）
```

造一个会「翻车」的测试文件 `leak_demo.py`：

```python
import os
AWS_KEY = "AKIAIOSFODNN7EXAMPLE"
gh_token = "ghp_0123456789abcdef0123456789abcdef01234567"
stripe = "sk_live_51HqyWYY2tX9b7e4c5d6f7g8h9i0j"
password = "correct horse battery staple"   # 低熵，应被过滤
db_url = "postgres://u:p@localhost/db"      # 无命中
```

运行：

```bash
python3 ~/.claude/skills/scan-secrets/scripts/scan_secrets.py leak_demo.py
```

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

```json
[
  { "file": "leak_demo.py", "line": 2, "rule": "AWS Access Key ID", "severity": "CRITICAL", "match": "AKIA…7EXAMPLE" },
  { "file": "leak_demo.py", "line": 3, "rule": "GitHub Token",       "severity": "CRITICAL", "match": "ghp_…4567" },
  { "file": "leak_demo.py", "line": 4, "rule": "Stripe Live Key",    "severity": "CRITICAL", "match": "sk_l…0j" }
]
```

注意 `password` 那行因熵值不足被正确过滤，这正是「脚本做确定性判断 + 模型做柔性解读」分工的价值。

在 Claude Code 里直接 `/scan-secrets leak_demo.py`，你会得到：严重度汇总表 + 逐条脱敏定位 + 对三条 CRITICAL 的「立刻轮换密钥并清理 git 历史」行动建议。

```mermaid
flowchart LR
    U[/scan-secrets src/] --> S[加载 SKILL.md]
    S --> D[注入 git diff 文件清单]
    D --> P[执行 scan_secrets.py]
    P --> J[输出 JSON 命中]
    J --> M[Claude 解读: 分级+脱敏+建议]
    M --> R[用户收到安全报告]
```

## 常见问题排查

- **Skill 不触发**：八成是 `description` 没覆盖用户的真实说法。检查是否第三人称、是否含关键词；用 `/skill-creator` 跑 eval 看准确率。
- **触发太频繁**：`description` 过宽。收窄「什么时候用」、加 `paths` 限定文件类型，或对纯动作型 Skill 加 `disable-model-invocation: true` 改为手动。
- **description 被截断**：合并后超过 1536 字符。砍掉冗余例句，保留最关键的能力 + 触发词。
- **改了不生效**：`SKILL.md` 文本应即时生效；若改的是脚本内容，确认路径用的是 `${CLAUDE_SKILL_DIR}`；若是 plugin 形态的 hooks/MCP，需 `/reload-plugins`。
- **脚本不执行**：检查 `allowed-tools` 是否预授权了 `Bash(${CLAUDE_SKILL_DIR}/scripts/scan_secrets.py *)`；命令行给的可执行位 `chmod +x`。
- **跨平台路径**：永远用正斜杠 `/`，即使在 Windows；`paths` glob 同理。

## 进阶方向

- **接入钩子**：把扫描塞进 `PreToolUse` 钩子，让任何 `git commit` 前自动拦截（命中即阻断），把「事后报告」升级为「事前护栏」。
- **历史扫描**：`git log -p | python3 scan_secrets.py -` 模式扩展 stdin 支持，扫已泄露进历史的密钥。
- **分发**：给 Skill 目录加 `.claude-plugin/plugin.json`，即可作为 plugin 打包，通过 marketplace 分发给团队；个人级则放 `~/.claude/skills/`，企业级走 managed settings 全员下发。

## 延伸阅读

- Claude Code 官方文档「Extend Claude with skills」：目录结构、frontmatter 全字段、动态注入与子代理执行
- Claude Platform「Skill authoring best practices」：description 写作铁律、渐进披露模式、反模式清单
- Anthropic 官方 Skills 仓库 `anthropics/skills`：可直接参考的真实 Skill 实现样例
- 内置 `/skill-creator` 文档：从零构建、修改、评测、基准测试到发布描述词优化的完整闭环

## 训练营适用性评估

- **安全/DevSecOps 训练营**：高度契合。本案例即是 commit 前密钥嗅探，可作为「左移安全」入门实操，预计教学时长 90 分钟（讲解 30 + 动手 45 + 评测调优 15）。
- **Claude Code 进阶训练营**：作为「Skill 开发」模块的标准教学案例，覆盖 frontmatter、脚本、渐进披露、eval 全链路，预计 120 分钟。
- **企业工程效能训练营**：可作为「把团队规范固化成可触发能力」的范本，重点讲 `paths`、`allowed-tools` 与分发策略，预计 60 分钟。
- **零基础训练营**：偏难。建议先做 Round 1 的入门 Skill，再上本课，否则 frontmatter 与渐进披露概念负荷过重。

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方 Skill 完整规范：目录结构、frontmatter 全字段、动态上下文注入、`context: fork` 子代理、字符串替换、生命周期与压缩
2. [Skill authoring best practices — Claude Platform Docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — description 第三人称写法、渐进披露三层模式、500 行上限、反模式清单
3. [Build a Claude Skill: YAML Frontmatter, SKILL.md Instructions — sjramblings](https://sjramblings.io/building-skills-for-claude-part-2/) — 动手教程，覆盖 SKILL.md 格式与 YAML 字段实战
4. [Claude Code Skill Frontmatter: Every YAML Option Explained — allahabadi.dev](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — 每个 frontmatter 字段的逐项深度解读
5. [anthropics/skills — GitHub](https://github.com/anthropics/skills) — Anthropic 官方维护的真实 Skill 仓库，可对照阅读工程化写法

> [!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)
