你以为正则里的 ^ 和 $ 永远只认字符串开头和结尾?Firefox 155 用两个锚点把它们彻底翻了一个面——multiline 模式下的行为从此不一样了

写过正则的人大概都踩过这个坑:写了一个 /^foo$/ 以为它只匹配整个字符串的 “foo”,结果加上 /m flag 之后,字符串里的每一行开头和结尾都被它认领了—— multiline 模式下的 ^ 和 $ 从「字符串边界」变成了「行首和行尾」,这个行为差异让很多安全相关的正则悄悄失效。

今天,Firefox 155 Nightly 带来了 RegExp Buffer Boundaries 提案的两个新锚点:A 和 z。它们的意义非常直接——永远只认整个输入的绝对开头和绝对结尾,不受 multiline flag 影响。

两个新锚点:A 和 z

这个提案(tc39.es/proposal-regexp-buffer-boundaries,Stage 3)在全球多数正则引擎(Perl、PCRE、.NET、Python re、Java、Go、Rust regex)中早已是标准功能,唯独 JavaScript 一直没有。现在 Firefox Nightly 155 率先实现了它们:

// 传统 ^/$ 在 multiline 模式下的行为
const re1 = /^foo$/mu;
re1.test("foo");       // true
re1.test("foo
bar");  // true ← 误匹配!$ 认了行尾

// A/z 永远只认整个输入的绝对边界
const re2 = /Afooz/mu;
re2.test("foo");       // true
re2.test("foo
bar");  // false ← 正确拒绝

对比一目了然:A 等价于「字符串最前面」,z 等价于「字符串最后面」,加不加 /m flag 都不会变。

为什么这件事重要

最典型的受害场景是输入验证。很多后端用这样的正则限制输入格式:

// 假设这是一个密码强度检查
const valid = /^[a-zA-Z0-9]+$/m.test(userInput);

如果攻击者输入 "safe <script>alert(1)</script>",multiline 模式下 ^ 和 $ 匹配了每一行的开头和结尾,这个恶意 payload 会通过验证——这在某些解析逻辑下会导致 XSS。

用 A...z 替换就能彻底封堵:

// 更安全的写法
const valid = /A[a-zA-Z0-9]+z/u.test(userInput);

同样的问题也出现在日志解析、数据格式校验、代码扫描等场景中。

语法细节

必须配合 /u 或 /v flag 使用,否则 A 和 z 在老版本里只是普通字符(字面量 “A” 和 “z”):

/Afooz/u.test("foo");  // true
/Afooz/.test("foo");   // false — 没有 u flag,A 是字符 "A"

z 匹配输入的最末端(无尾随换行),Z 在 2026 年 5 月重新加入提案(Stage 2.7),用于匹配可选尾随换行符的场景:

/endZ/u.test("The end
");     // true
/endz/u.test("The end
");     // false — z 不认尾随换行

浏览器支持现状

  • Firefox 155 Nightly:已支持,需设置 javascript.options.experimental.regexp_buffer_boundaries = true
  • Chrome:未支持(tracking bug)
  • Safari:未支持
  • Node.js:未支持

这是一个典型的「语言层能力补全」,Stage 3 意味着规范文本已经完成,浏览器实现跟上后离正式发布不远。

迁移路径

渐进增强是最安全的写法:

function isValidInput(str) {
  // 优先用原生 A/z
  try {
    return /A[a-zA-Z0-9]+z/iu.test(str);
  } catch (e) {
    // 降级:手动在首尾加 ^$
    return /^[a-zA-Z0-9]+$/u.test(str) && !str.includes('
');
  }
}

下一步: 检查你项目里所有带 /m flag 的正则,逐一确认 ^...$ 的语义是否真的是「匹配行首行尾」而非「匹配输入首尾」——如果是后者,换成 A...z 是五分钟就能完成的安全加固。

评论区

0 条评论

登录后可评论。

铁锈·Rust工具链 224 阅读