首页 › tutorials › 从零造正则回溯扫描Skill

概述

本教程以"正则灾难性回溯(ReDoS)风险扫描"为案例,带你从零构建一个完整的 Claude Code 自定义 Skill,覆盖目录规划、SKILL.md frontmatter 编写、渐进式披露三层结构、辅助脚本、参考子文档与调试验证全流程。学完后你能独立设计任意领域的 Skill,并掌握让 Skill"该出手时才出手"的触发词写作精髓。适合已会用 Claude Code 跑基本任务、想封装团队可复用工作流的开发者。

前置条件


一、先搞清楚:Skill 到底是什么

很多人把 Skill、自定义命令、子代理、MCP 服务器混为一谈。它们的本质区别在于"加载时机"与"承载形态"。

Skill 是一组放在文件系统里的指令包,核心是一份 SKILL.md。模型平时不读它,只在判断当前任务与某条 Skill 的描述匹配时,才把它加载进上下文。这正是"渐进式披露"的精髓——平时零成本,命中才付费。

形态 承载 触发 适合场景
Skill 文件系统指令包 模型按 description 自动判断 领域工作流、专家知识封装
自定义命令 slash 命令文件 用户输入 /名字 用户主动调用的固定流程
子代理(subagent) 独立上下文的代理 主代理委派 隔离上下文、并行拆分
MCP 服务器 外部进程 + 工具协议 模型调用 tool 接入外部系统、实时数据

一句话记忆:命令是"我要做",MCP 是"我需要数据",子代理是"分身去做",Skill 是"我会做,且你该知道我什么时候该做"。

二、案例预览:regex-guard 要解决什么

正则表达式写不好,会埋下 ReDoS 这颗定时炸弹。一段看似无害的 ^(a+)+$ 在面对精心构造的输入时,匹配时间会随字符串长度指数级膨胀,几十字符就能让一个 Node 服务 CPU 打满、整条请求线卡死。历史上 Stack Overflow、Cloudflare 都因此发生过线上事故。

regex-guard 这个 Skill 的目标很简单:扫描代码库里的正则字面量,找出有回溯爆炸风险的写法,给出回溯路径分析与安全重写建议。 它足够小、足够具体,却能完整展示一个生产级 Skill 的全部结构。

三、第一步:规划目录与触发场景

Skill 可以放在两个位置:用户级 ~/.claude/skills/<name>/(所有项目可用)或项目级 .claude/skills/<name>/(随仓库共享给团队)。我们选项目级,方便版本管理与团队复用。

mkdir -p .claude/skills/regex-guard/{scripts,reference}
cd .claude/skills/regex-guard
touch SKILL.md scripts/detect_regex_risks.py reference/evil-patterns.md reference/rewrite-patterns.md

最终目录长这样:

.claude/skills/regex-guard/
├── SKILL.md                       # 主入口:frontmatter + 精简工作流
├── scripts/
│   └── detect_regex_risks.py      # 启发式扫描脚本
└── reference/
    ├── evil-patterns.md           # 危险模式详解(第三层,按需读)
    └── rewrite-patterns.md        # 安全重写对照表(第三层,按需读)

预期结果:Claude Code 启动时会扫描该目录,发现 SKILL.md 即注册为一个候选 Skill,但此时还不会加载正文。

常见问题:目录名必须与 frontmatter 的 name 一致,否则部分版本会出现"注册成功但不触发"的幽灵状态。

四、第二步:写 frontmatter——description 是灵魂

整个 Skill 最关键的字段不是正文,而是 frontmatter 里的 description。模型只看 description 来决定要不要加载你的 Skill,写得含糊,Skill 就永远不会被触发。

---
name: regex-guard
description: 扫描代码库中的正则表达式字面量,识别可能导致灾难性回溯(ReDoS)的性能风险模式(嵌套量词、重叠交替叠加量词、贪婪通配叠加等),输出风险位置、回溯路径分析与可落地的安全重写建议。当用户排查正则性能问题、审查用户输入校验逻辑、做安全审计、或抱怨"某个校验函数偶尔卡死"时使用。
---

写好 description 的四条铁律:

  1. 第一句说能力:它"做什么",而不是"它是什么"。
  2. 塞入触发关键词:把用户可能脱口而出的词埋进去——"正则""regex""性能""卡死""ReDoS""输入校验""安全审计"。
  3. 明确使用场景:用"当……时使用"收尾,给模型一个判断锚点。
  4. 长度克制:通常 1–3 句、不超过 500 字符。描述越长越泛,反而越容易误触发或漏触发。

可选字段还包括 disable-model-invocation: true(禁止自动触发,仅 /skill 手动调用)、version、author 等。allowed-tools 可声明限制,但要注意它主要是意图声明,不要把它当成安全边界——真正要收紧权限应靠 settings.json 的权限配置。

常见问题:描述写成 "一个检查正则的工具" 这种废话,模型根本无从判断何时该用。务必具体到场景。

五、第三步:写 SKILL.md 正文——保持精简

正文是第二层披露。原则是只放工作流骨架,细节下沉到 reference。因为正文每次触发都会进入上下文,写得越长,每个会话的 token 成本越高。

# 正则回溯风险扫描(regex-guard)

## 何时使用
用户提交了用户输入校验、遇到正则匹配卡顿、或要求做一次正则/安全审查时,主动扫描仓库。

## 工作流
1. 调用 `scripts/detect_regex_risks.py <目录>` 跑启发式初筛,拿到可疑清单。
2. 对每个可疑正则,结合上下文判断是否真有可控输入路径。
   - 若需要判断精确的回溯路径与星级,读取 `reference/evil-patterns.md`。
   - 若需要给出安全重写,读取 `reference/rewrite-patterns.md`。
3. 输出报告:文件:位置、风险等级(高/中/低)、一句话原因、重写建议。

## 输出格式
按风险等级排序的 Markdown 表格,高风险项附 2–3 行修复 diff 示例。

## 注意
脚本仅为启发式,会有误报。最终判断必须结合输入是否可控——只处理常量字符串的正则
即便模式危险也无需告警,请勿制造噪音。

注意最后那段"何时读取 reference"的指引——这是渐进式披露的胶水。正文告诉模型"危险模式详解在 reference/evil-patterns.md,需要时去读",模型只在实际需要深度分析时才加载那份文档,平时不占上下文。

六、第四步:辅助脚本 detect_regex_risks.py

脚本负责把"重复且确定"的活儿从模型身上剥离——模型擅长判断,脚本擅长穷举。下面是一份精简但可运行的启发式扫描器:

#!/usr/bin/env python3
"""Extract regex literals from source and heuristically flag ReDoS risks.
This is a heuristic first-pass; precise backtracking analysis is left to the model
after it reads reference/evil-patterns.md.
"""
import re, sys, pathlib

# Heuristic dangerous patterns
DANGEROUS = [
    (r'\([^)]*[+*?][^)]*\)[+*?]',      'nested quantifier, e.g. (a+)+'),
    (r'\((?:[^|)]+\|)+[^|)]+\)[+*?]',  'overlapping alternation + quantifier, e.g. (a|a)*'),
    (r'(?:\.\*|\.\+|\.\?){2,}',        'stacked greedy wildcards, e.g. .*.*'),
]

# Match /pattern/flags and new RegExp("pattern")
LITERAL = re.compile(r'/(?:[^/\\\n]|\\.)+/[gimsuy]*')
NEWRE   = re.compile(r'new\s+RegExp\(\s*[\'"]([^\'"]+)[\'"]')

def scan(text):
    found = [(m.start(), m.group(0).strip('/').split('/')[0]) for m in LITERAL.finditer(text)]
    found += [(m.start(), m.group(1)) for m in NEWRE.finditer(text)]
    risks = []
    for off, pat in found:
        for rule, reason in DANGEROUS:
            if re.search(rule, pat):
                risks.append((off, pat, reason))
    return risks

if __name__ == '__main__':
    root = pathlib.Path(sys.argv[1])
    exts = {'.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.java'}
    files = [p for p in root.rglob('*') if p.suffix in exts
             and 'node_modules' not in p.parts and '.git' not in p.parts]
    total = 0
    for f in files:
        text = f.read_text(encoding='utf-8', errors='ignore')
        for off, pat, reason in scan(text):
            line = text[:off].count('\n') + 1
            print(f'{f}:{line}  /{pat}/  -> {reason}')
            total += 1
    print(f'---- {total} suspicious risk(s) ----')

预期结果:在一个含 const email = /^(a+)+$/ 的测试文件上运行,会输出该行与原因。

chmod +x scripts/detect_regex_risks.py
python3 scripts/detect_regex_risks.py ./src
# src/validators.js:42  /^(a+)+$/  -> nested quantifier, e.g. (a+)+
# ---- 1 suspicious risk(s) ----

常见问题:脚本误报是常态。比如 (\d{4})+ 虽然形似嵌套量词,但 \d{4} 是定长,没有回溯。这正是要把"最终判断"留给模型、把"精确判据"放进 reference 的原因——脚本只负责把候选捞出来。

七、第五步:reference 子文档——第三层披露

reference/evil-patterns.md 存放需要专业知识才能写好的判据。这部分平时完全不加载,只有模型在分析某个可疑正则时,按正文指引"按需"读取。它可以是详尽的、篇幅不受限的。

# ReDoS 危险模式判据

## 星级标准(基于回溯步数的理论分析)
- ⭐⭐⭐ 指数级:嵌套量词叠加,如 (a+)+、(a*)*,输入长度 n 时回溯 O(2^n)
- ⭐⭐   多项式级:重叠交替叠加量词,如 (a|a)*,回溯 O(n^2)
- ⭐     线性放大:贪婪通配叠加,如 .*.*,常数级恶化但通常可接受

## 关键判别:输入是否可控
模式的危险度 = 回溯复杂度 × 输入可控度。
- 若正则只匹配代码内常量 → 即便 ⭐⭐⭐ 也无需告警
- 若正则匹配 request.body / 用户上传文本 → ⭐ 也要修

## 常见误报
- (\d{4})+ :定长子项,无回溯,安全
- (?:abc)+ :定长子项,安全
- 字符类 [a-z]+ 后跟 + :字符类不重叠时安全

reference/rewrite-patterns.md 则是修复对照表,例如把 ^(a+)+$ 改写成原子组 ^(?>a+)$ 或换用 string-argv 这类线性匹配。这两份文档共同构成了 Skill 的"专家大脑"。

八、第六步:安装、调试与验证

Skill 文件就位后,重启或新开一个 Claude Code 会话即可自动注册。验证触发是否正常,是最容易踩坑的一环。

# 1. 确认 Skill 被发现
claude            # 进入会话后输入
> /skills         # 列出所有已注册 Skill,应能看到 regex-guard

调试触发率的三板斧:

  1. 不触发的诊断:用一句贴近 description 关键词的话测试,如"帮我看看 src 里的正则有没有性能隐患"。若仍不触发,说明 description 缺关键词或写得太宽泛。
  2. 强制调用:在会话里输入 /regex-guard 可手动激活(前提是未设 disable-model-invocation)。
  3. 观察加载日志:开启 ANTHROPIC_LOG=debug 环境变量,能看到 Skill 是否进入候选集、为何被选中或排除。

常见问题:改了 SKILL.md 却不生效——Claude Code 对 Skill 有缓存,需新开会话或重载。脚本路径要用相对 Skill 根目录的写法,模型默认在 Skill 目录下执行。

九、实战案例:在真实仓库上跑通

以一个 Express 项目为例,模拟一次完整的扫描:

# 项目结构
my-api/
├── src/
│   ├── validators.js   # 含 const phone = /^(\\d+)+$/
│   └── routes/user.js
└── .claude/skills/regex-guard/   # 我们的 Skill

在仓库根目录打开 Claude Code,自然语言提问:

帮我审查一下 src 目录里的输入校验,看看有没有正则方面的隐患。

模型应自动触发 regex-guard,调用脚本初筛、读取 reference 做精确判断,最终产出类似报告:

文件 行 正则 风险 等级 建议
src/validators.js 18 /^(\d+)+$/ 嵌套量词,指数级回溯 ⭐⭐⭐ 改写为 /^\d+$/ 或 ^(?>\d+)$

修复建议附带最小 diff:

- const phone = /^(\d+)+$/;
+ const phone = /^\d+$/;

至此一个"会自己判断、会调脚本、会查专家文档、会输出规范报告"的 Skill 就闭环跑通了。

工作流全景

flowchart TD
    A[用户提问:正则有隐患吗?] --> B{模型扫描所有 Skill 的 description}
    B -->|命中 regex-guard| C[加载 SKILL.md 正文 第二层]
    B -->|未命中| Z[零成本,不加载]
    C --> D[按正文工作流调用 scripts 扫描]
    D --> E{需要精确分析?}
    E -->|是| F[读取 reference/evil-patterns.md 第三层]
    E -->|否| G[直接输出报告]
    F --> H[读取 reference/rewrite-patterns.md]
    H --> G
    G --> I[Markdown 报告 + 修复 diff]

图中三条虚线对应渐进式披露的三个层级:平时只有 description 在场,命中后才加载正文,深入分析才读 reference。每一层都意味着上下文成本,所以"能下沉就下沉"是 Skill 设计的第一原则。

排错速查

延伸阅读

训练营适用性评估

参考来源

  1. 使用 skills 扩展 Claude - Claude Code Docs — 官方 Skill 创建、管理与共享权威指南
  2. Claude Code 的 SKILLS 技能渐进式披露实现原理解析 - 潘锦 — 三层加载机制的源码级剖析
  3. Claude Code Skill Frontmatter: Every YAML Option Explained — frontmatter 全字段逐一详解
  4. Claude Skill(技能)构建完全指南 - 知乎 — 中文社区完整的 Skill 构建教程
  5. continuous-claude-v3/dead-code/SKILL.md - GitHub — 真实生产级静态分析 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学通信知识星球海报-扫码加入