03 / 指南
安全测试正则表达式的工作流
面向 JavaScript 正则表达式的编写—测试—重构循环:真实样本数据、灾难性回溯防护、锚定纪律、标志位语义,以及为什么在浏览器中测试能让模式保持私密。
循环:编写、测试、重构
可靠的正则从来不是一次写成的。从能匹配一个真实例子的最小模式开始,运行它,再对照下一个例子放宽或收紧。样本文本是规格,模式是实现。
反馈即时,循环才快:每次击键都重新求值的测试器把每次修改变成一个有即时答案的实验,这就是为什么专用工作区胜过手工反复跑脚本。
- 从样本开始对着具体样本写模式,而不是凭记忆:粘贴一行你打算解析的日志或文档的真实内容。
- 测试每次修改每次修改后都测试——正则没有编译警告,唯一的反馈就是它匹配了什么、漏掉了什么。
- 为读者重构通过之后为阅读而重构:命名捕获组与非捕获组让能工作的模式对下一位读者也能存活。
敬畏灾难性回溯
JavaScript 的正则引擎会回溯:匹配失败时,它会用不同的量词长度重试。(a+)+ 这类嵌套量词作用在险些匹配的字符串上,会让重试次数指数级爆炸——在良好输入上瞬间作答的模式,在恶意输入上可能跑好几年。攻击者称之为 ReDoS。
JavaScript 正则表达式测试器约束了爆炸半径:模式在专用 Web Worker 中以 650 毫秒预算执行,失控的表达式会被报告为明确的超时,而不是冻结标签页。如果你的模式在真实输入上撞上这条上限,把它当作一个发现——同样的输入在生产环境会卡住事件循环。
- 嵌套量词经典形态是量词组内含量词——(a+)+、(\w+)*——同一字符串的许多种划分都能满足内部模式。
- 歧义交替歧义交替也会引爆:(a|aa)+ 给引擎两种匹配每个额外字符的方式,搜索树随字符数翻倍。
- 结构性修复修复是结构性的:让内部匹配无歧义、用显式长度限制约束,并且务必测试险些匹配的输入,而不只是合法输入。
先锚定,再信任匹配
能找到子串的模式,不等于能校验值的模式。/\d+/ 会在 abc123xyz 内部匹配;/^\d+$/ 只接受纯数字。大多数校验缺陷是缺少锚点,而大多数提取缺陷是不该有的锚点。
- 开头与结尾^ 与 $ 把匹配钉在字符串的开头与结尾——开启多行标志时则钉在每一行,这会悄悄改变“整个输入”的含义。
- 词边界\b 词边界防止短模式匹配进更长的单词:\bcat\b 找的是那只动物,而不是 concatenate 里的 cat。
- 搜索还是校验一开始就决定搜索还是校验:提取模式通常不要锚点,校验模式几乎总是两个都要。
用真实样本数据测试
用三个整洁例子证明过的模式,会在生产环境的第四个真实输入上失败。构建反映输入分布的样本集:最长的合法值、空字符串、空白变体、Unicode 名字,以及那个几乎该匹配但必须不匹配的险些样本。
- 险些样本包含险些样本:邮箱模式应该用 a@b 和 a b@c.d 测试,而不只是格式良好的地址。
- Unicode数据里有 Unicode 就测 Unicode:\w 和 . 在 u 标志下含义改变,带重音的名字会戳破 ASCII 时代的假设。
- 重跑整个集合把样本集放在模式旁边;模式一变,重跑整个集合,而不只是促成修改的那个用例。
标志位改变的是语言,不只是搜索
标志位是模式含义的一部分。g 决定你拿到第一个匹配还是全部,i 折叠大小写,m 把 ^ 和 $ 重新锚定到行,s 让点号跨行,u 把引擎切换为语法更严格的 Unicode 感知模式。
工作区按你选择的标志位求值,列出每个匹配的起止偏移、带各自位置的编号与命名捕获组,以及实时的替换预览——标志位的变化体现为结果上的具体差异,而不是理论。
- g 与 lastIndex没有 g,test() 与 match() 只看到第一个命中;有了 g,正则对象携带 lastIndex 状态,反复调用 test() 会交替返回结果——一个臭名昭著的生产缺陷。
- s 放宽点号s(dotAll)让 . 匹配换行,全局启用时可能悄悄放大旧模式的胃口。
- u 及其伙伴u 让字符类与转义符合 Unicode 并拒绝歧义的旧式语法;测试器还支持 d(匹配索引)、y(粘性)与 v(Unicode 集合)。
让模式与样本留在本地
测试数据往往就是真实数据:客户邮箱、内部日志行、事故报告里的标识符。把它粘进托管测试器就是把它送进别人的基础设施。基于浏览器的测试器把整个会话留在标签页里——模式与样本文本由你自己设备的引擎求值,绝不传输。
本地运行时对上限很明确:模式最长 4,096 字符,样本文本最长 100,000 字符,替换串最长 20,000,单次运行最多报告 1,000 个匹配。这些限制面向开发与调试;批量日志分析属于本地脚本,而不是浏览器标签页。
只复制出你需要的东西——完成的模式与替换串——把样本数据留在它开始的地方:你的机器上。
在本地测试一个模式提取出的数据通常落进 JSON。
模式从原始文本中抽出值之后,用一套确定性的本地流程检视并 diff 得到的负载。