本教程带你从零构建一个完整可用的 Claude Code Skill(技能)——一个能自动嗅探源码里硬编码密钥(AWS Key、GitHub Token、私钥、Slack Token 等)的「源码密钥嗅探器」。你将掌握 Skill 的目录结构、YAML frontmatter 字段、description 触发词写法、脚本渐进披露、动态上下文注入、allowed-tools/paths 权限控制,以及用 skill-creator 做评测打磨触发的完整闭环。
适用人群:已用过 Claude Code 基本对话、写过简单脚本的工程师;想把自己重复粘贴的检查清单固化成「可复用、可触发、可分发」能力的开发者。
${CLAUDE_PROJECT_DIR} 等较新特性)git init 一个空目录)当你反复把同一段「检查清单」或「多步骤流程」粘贴到对话里时,就该把它做成 Skill。Skill 和你熟悉的三种配置有清晰分工:
| 载体 | 加载时机 | 适合放什么 |
|---|---|---|
CLAUDE.md |
每次会话始终在上下文 | 项目事实、约定、不变的偏好 |
| Skill | 相关时才加载全文 | 可触发的过程、检查清单、带脚本的流程 |
自定义命令 .claude/commands/ |
你手动 / 调用 |
已被 Skill 取代,二者等价但 Skill 更强 |
| MCP 服务器 | 始终注册为工具 | 需要长连接外部 API 的工具 |
关键区别:Skill 的元数据(name + description)在启动时预载,全文只在被触发时才进入上下文,脚本更是只看输出不看源码。这意味着你可以塞进一大堆「平时用不上、用时必须有」的规则,平时几乎零成本。
Skill 的精妙之处在于「按需加载」的三层结构。理解它,你就理解了 Skill 的组织哲学:
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 行以内。
动手前先回答三个问题,这决定了 frontmatter 怎么写:
/scan-secrets,也允许 Claude 在谈安全审查时自动调用,但绝不能让它自动 git push。Skill 的命令名直接来自目录名(而非 frontmatter 的 name)。先建目录:
mkdir -p ~/.claude/skills/scan-secrets
放在 ~/.claude/skills/ 是个人级,所有项目可用;只想给某个项目用就放该项目的 .claude/skills/scan-secrets/。
接着写 SKILL.md。frontmatter 最关键的是 description——它是 Claude 从上百个 Skill 里「选中你」的唯一依据。
---
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 写作四铁律(来自官方最佳实践):
when_to_use 合并后在 Skill 列表里截断到 1536 字符。别写小作文。其余字段说明:
- paths 限定只在编辑匹配文件时才自动激活,避免你写文档时也跳出来。
- allowed-tools 预授权扫描脚本和只读 git 命令,免去每次确认。
- ${CLAUDE_SKILL_DIR} 解析为 Skill 自身目录,无论当前工作目录在哪都能定位到自带脚本。
frontmatter 下方是 Markdown 正文,即「Skill 被触发后 Claude 读到的操作手册」。把动态数据用 !command`` 在加载时预先注入,Claude 看到的是真实数据而非命令:
## 当前改动(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: ...。把确定性逻辑交给脚本,而不是让模型「现场推理正则」。这样既准确(正则是死的,模型可能漏匹配),又省 token(脚本源码不进上下文,只看输出)。在 ~/.claude/skills/scan-secrets/scripts/scan_secrets.py 写:
#!/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)
要点:
password="test" 这类低熵误报。主文件里只放「读 rules.md 获取完整规则」的引用,长内容留在 ~/.claude/skills/scan-secrets/rules.md。示例片段:
# 嗅探规则与误报排除
## 严重度定义
- 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。引用层级越深,模型越可能漏读。
你已经通过 frontmatter 完成了两件事:paths 限定激活时机、allowed-tools 预授权工具。再理解两个对立开关,覆盖绝大多数编排需求:
| 配置 | 效果 | 适用场景 |
|---|---|---|
| 默认 | 用户可 /x,Claude 也可自动调用 |
你的嗅探器:默认 |
disable-model-invocation: true |
仅用户手动,Claude 不自动触发 | /deploy、/commit 这类有副作用的 |
user-invocable: false |
仅 Claude 自动,用户菜单看不到 | 纯背景知识(如遗留系统说明) |
本 Skill 故意保持默认——既支持 /scan-secrets 手动,也能在用户聊「提交前帮我看看安不安全」时被自动选中。
改完文件无需重启——Claude Code 对 Skill 目录有实时变更检测,保存即生效(仅 SKILL.md 文本层面;若 Skill 同时是 plugin 改了 hooks 需 /reload-plugins)。
进入一个 git 仓库启动 claude,分别验证:
/scan-secrets src,按 argument-hint 提示传路径。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:
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" # 无命中
运行:
python3 ~/.claude/skills/scan-secrets/scripts/scan_secrets.py leak_demo.py
预期输出(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 历史」行动建议。
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[用户收到安全报告]
description 没覆盖用户的真实说法。检查是否第三人称、是否含关键词;用 /skill-creator 跑 eval 看准确率。description 过宽。收窄「什么时候用」、加 paths 限定文件类型,或对纯动作型 Skill 加 disable-model-invocation: true 改为手动。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 支持,扫已泄露进历史的密钥。.claude-plugin/plugin.json,即可作为 plugin 打包,通过 marketplace 分发给团队;个人级则放 ~/.claude/skills/,企业级走 managed settings 全员下发。anthropics/skills:可直接参考的真实 Skill 实现样例/skill-creator 文档:从零构建、修改、评测、基准测试到发布描述词优化的完整闭环paths、allowed-tools 与分发策略,预计 60 分钟。context: fork 子代理、字符串替换、生命周期与压缩[!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 等场景全覆盖。
