首页 › tutorials › 造硬编码密钥扫描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 基本对话、写过简单脚本的工程师;想把自己重复粘贴的检查清单固化成「可复用、可触发、可分发」能力的开发者。

前置条件

Skill 到底解决了什么问题

当你反复把同一段「检查清单」或「多步骤流程」粘贴到对话里时,就该把它做成 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 行以内。

Step 1:规划你的 Skill

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

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

Step 2:建目录、写 frontmatter

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 写作四铁律(来自官方最佳实践):

  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 看到的是真实数据而非命令:

## 当前改动(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),按需阅读,不必每次加载。

这里有两个高价值特性:

Step 4:抽离扫描脚本(渐进披露第二层)

把确定性逻辑交给脚本,而不是让模型「现场推理正则」。这样既准确(正则是死的,模型可能漏匹配),又省 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)

要点:

Step 5:支撑文件 rules.md(渐进披露第三层)

主文件里只放「读 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。引用层级越深,模型越可能漏读。

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:

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[用户收到安全报告]

常见问题排查

进阶方向

延伸阅读

训练营适用性评估

参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方 Skill 完整规范:目录结构、frontmatter 全字段、动态上下文注入、context: fork 子代理、字符串替换、生命周期与压缩
  2. Skill authoring best practices — Claude Platform Docs — description 第三人称写法、渐进披露三层模式、500 行上限、反模式清单
  3. Build a Claude Skill: YAML Frontmatter, SKILL.md Instructions — sjramblings — 动手教程,覆盖 SKILL.md 格式与 YAML 字段实战
  4. Claude Code Skill Frontmatter: Every YAML Option Explained — allahabadi.dev — 每个 frontmatter 字段的逐项深度解读
  5. anthropics/skills — GitHub — 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学通信知识星球海报-扫码加入