本教程带你从零开发一个名为 secret-scanner 的自定义 Claude Code Skill,它能在代码库里自动发现硬编码的密钥、API Key、令牌和私钥,并产出一份按严重度排序的修复报告。学完你将掌握 SKILL.md 的结构、YAML frontmatter 各字段、渐进披露(progressive disclosure)设计、捆绑脚本与模板的组织方式,以及如何让技能精准触发、安全受限地运行。适合已经用过 Claude Code、想把重复性工作固化成可复用能力的中级开发者。
Claude Code 的 Skill 本质上是一个文件夹,核心文件是 SKILL.md。文件夹里还可以放脚本、模板、参考文档等任意资产。Claude 不会一次性把整个技能加载进上下文,而是采用渐进披露(progressive disclosure)机制:
SKILL.md frontmatter 里的 name 和 description;description 时,Claude 才把 SKILL.md 正文读进上下文;这套机制决定了写 Skill 的两条铁律:
description 要写成"何时该用"——它是技能唯一的触发入口,写得越具体、越带场景词,触发越精准。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 放个人级,因为它在任何项目里都用得上。
创建目录并新建入口文件:
mkdir -p ~/.claude/skills/secret-scanner/{scripts,templates,rules}
写入 ~/.claude/skills/secret-scanner/SKILL.md 的 frontmatter 部分:
---
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...),列举用户可能说的触发词("扫描密钥"、"审计安全"、"提交前检查");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 字,把"怎么做"压缩成清晰的工作流,细节丢给子文件:
# 密钥泄露扫描
当用户请求扫描密钥 / 检查泄露凭证 / 审计安全时激活本技能。
## 工作流程
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 写入一个可独立运行的正则扫描器:
#!/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 能直接调用:
chmod +x ~/.claude/skills/secret-scanner/scripts/scan.py
这段脚本的设计要点:
dirnames[:] = ... 原地修改实现目录剪枝,避免 os.walk 误入 node_modules 造成扫描爆炸;rules/patterns.md 示例(只放 Claude 需要判断误报时才读的细节):
# 密钥匹配规则与误报处理
## 已知误报
- 示例代码、文档、单测 fixture 里的假密钥(如 `AKIAIOSFODNN7EXAMPLE`)应忽略
- 环境变量**引用**(`os.getenv("AWS_KEY")`)不是泄露,只有**赋值字面量**才算
## 处理策略
- 若文件名含 `test/`、`example/`、`docs/` 或后缀 `.md`,降低严重度一档
- 对 Generic Secret 规则,先确认右侧是否为占位符(`your-key-here`、`xxx`),是则跳过
templates/report.md 报告骨架:
# 🔐 密钥泄露扫描报告
扫描时间:{{date}} 扫描目录:{{target}}
**发现 {{count}} 处疑似泄露**(🔴 紧急 {{critical}} / 🟠 高危 {{high}} / 🟡 中危 {{medium}})
## 紧急处理清单
> 含私钥或生产令牌的项目,请立即在各服务商后台**撤销并轮换**对应凭证。
| 严重度 | 文件 | 行号 | 规则 | 片段(已脱敏) |
|--------|------|------|------|----------------|
{{rows}}
## 修复建议
- [ ] 将所有硬编码密钥迁移到环境变量或密钥管理服务(AWS Secrets Manager / Vault)
- [ ] 撤销已泄露的凭证并轮换
- [ ] 在 `.gitignore` 中加入 `.env`,并考虑接入 pre-commit 的密钥扫描钩子
新建测试目录,故意埋几个假密钥:
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
先独立验证脚本:
python3 ~/.claude/skills/secret-scanner/scripts/scan.py /tmp/demo-app
预期输出(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,输入一句自然语言:
帮我扫一下当前项目里有没有泄露的密钥
预期发生的事:模型识别到"扫描密钥/泄露"等触发词,自动加载 secret-scanner 的正文,然后执行 scan.py,最后按 templates/report.md 写出 SECRET-SCAN-REPORT.md。
你也可以显式调用:直接输入技能名 secret-scanner,或用它对应的斜杠形式。若想强制只能手动触发、不允许自动唤起,在 frontmatter 加一行:
disable-model-invocation: true
默认情况下技能继承会话的全部工具。出于安全考虑,应限制 secret-scanner 只能读文件、只能跑它自己的脚本。在 frontmatter 增加:
allowed-tools: Bash(python3 scripts/scan.py:*), Read, Write(SECRET-SCAN-REPORT.md)
这样该技能不能执行 rm、git push、联网等危险操作,即便被恶意输入诱导也无法越权。
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: 报告摘要 + 修复建议
把以上片段拼起来,一个完整可运行的技能就成型了。最终交付:
secret-scanner Skill;RULES 里加正则、往 rules/ 里加误报策略即可持续增强,无需改动入口。进阶玩法:
<repo>/.claude/skills/,让整个团队 clone 仓库即拥有该能力;settings.json 里配 PreToolUse 钩子,每次提交前自动跑一次扫描,命中则阻断;plugin.json 打包,发布到团队市场(参见 Plugin 打包教程)。| 维度 | 评估 |
|---|---|
| 适合训练营 | ✅ 中级开发者训练营、安全工程训练营、AI 工程化训练营 |
| 教学时长 | 约 90–120 分钟(含动手造测试场与联调) |
| 学员产出 | 每人一个可运行的 secret-scanner 技能 + 扫描报告 |
| 前置衔接 | 建议先上"读懂智能体循环"或"四层配置装通 Claude Code" |
| 后续延伸 | 可衔接 Plugin 打包、Hook 自动化、MCP 服务器开发等进阶主题 |
name、description、allowed-tools、disable-model-invocation 等 15 个 frontmatter 字段。[!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 等场景全覆盖。
