Regolith:从根本上免疫 ReDoS 攻击的服务器端正则表达式库

你是否曾担心过编写的正则表达式可能成为服务中的安全漏洞?是否听说过“正则表达式拒绝服务攻击”(ReDoS)却不知其具体含义?今天我们将深入探讨一个能够从根本上解决这一问题的开源工具——Regolith。

什么是 ReDoS 攻击?

正则表达式拒绝服务(ReDoS)攻击是一种特殊类型的拒绝服务攻击,它利用的是某些正则表达式引擎在处理特定模式时的设计缺陷。当恶意攻击者精心构造的输入与 vulnerable 的正则表达式模式相遇时,会导致引擎进入指数级增长的计算复杂度,从而消耗大量服务器资源,使服务变得不可用。

想象一下,一个简单的表单验证正则表达式,正常情况下处理用户输入只需几毫秒,但在遭受 ReDoS 攻击时,可能会使服务器卡在该计算上长达数小时!这种攻击之所以危险,是因为它不需要大量请求流量,只需要一个精心构造的请求就能达到攻击效果。

现有正则表达式引擎的问题

大多数编程语言(包括 JavaScript 和 TypeScript)内置的正则表达式引擎都存在这个问题。它们使用的是基于回溯的算法,在最坏情况下会出现指数级的时间复杂度增长。

研究表明,当输入规模达到 30 个字符时,JavaScript 的标准 RegExp 引擎处理某些恶意模式可能需要超过 10 秒的时间。相比之下,线性时间复杂度的引擎处理同样的输入只需要不到 1 毫秒。

Regolith 的解决方案

Regolith 是一个服务器端 TypeScript 和 JavaScript 库,它通过使用 Rust 编写的线性时间复杂度正则表达式引擎,从根本上免疫了 ReDoS 攻击。

技术原理

Regolith 的核心在于使用了 Rust 语言的高性能正则表达式库。Rust 的正则表达式库经过精心设计,故意排除了会使引擎容易受到 ReDoS 攻击的功能特性——主要是回溯引用(backreferences)和环视(look-around)操作。虽然这意味着某些高级功能不可用,但换来的却是执行时间的线性保证。

为什么选择 Rust?

Rust 语言以其内存安全性和高性能而闻名。Rust 的正则表达式库在社区中已经得到了广泛验证和信任,被用于许多对性能和安全要求极高的场景。Regolith 通过 napi-rs 创建 JavaScript 绑定,让 JavaScript 和 TypeScript 开发者也能享受到这一强大引擎的优势。

直接替换体验

Regolith 最令人称赞的特点之一是它试图成为 RegExp 的直接替代品,几乎不需要修改现有代码即可使用。这意味着你可以轻松地将现有项目中的正则表达式操作迁移到 Regolith,立即获得对 ReDoS 攻击的免疫力。

Regolith 直接替换示意图

实际应用案例

现实中的 ReDoS 漏洞

2025 年发现的 CVE-2025-5889 漏洞影响了 brace-expansion 库,这是一个被 4250 万个 GitHub 项目使用的流行库。这个漏洞本身不是该项目的过错,而是语言层面允许这类问题存在的必然结果。

修复这样一个漏洞需要巨大的生态系统努力:需要向后兼容多个版本,每个使用该库的项目都需要更新、测试和部署。如果每个人都保持软件更新,这将意味着 4250 万个拉取请求,大约 4250 万分钟的构建时间,以及可能超过 4200 万工程师分钟的工作量。

而使用 Regolith 这样的免疫库,可以一次性解决所有这类潜在问题,为整个生态系统节省大量资源。

如何开始使用 Regolith

安装

安装 Regolith 非常简单,只需要运行以下命令:

npm i @regolithjs/regolith

基本使用

使用 Regolith 与使用标准 RegExp 非常相似:

import { Regolith } from '@regolithjs/regolith';

// 创建正则模式
const pattern = new Regolith("^\\d+$");

// 使用模式进行测试
pattern.test("12345");  // 返回 true
pattern.test("Hello");  // 返回 false

完整功能示例

Regolith 支持所有常见的正则表达式操作:

匹配示例

import { Regolith } from '@regolithjs/regolith';

const pattern = new Regolith('crab', 'g');
console.log(pattern.test('my crab ferris')); // 输出: true

查找所有匹配

const sentence = 'crab, snail, crab';
const crabPattern = new Regolith('crab', 'g');

console.log(crabPattern.match(sentence));
// 输出: ['crab', 'crab']

替换操作

const sentence = 'crab, snail, crab';
const crabPattern = new Regolith('crab', 'g');

console.log(crabPattern.replace(sentence, 'snake'));
// 输出: 'snake, snail, snake'

搜索操作

const sentence = 'crab, snail, crab';
const snailPattern = new Regolith('snail');

console.log(snailPattern.search(sentence));
// 输出: 6 (找到'snail'的位置索引)

分割操作

const splitPattern = new Regolith('[,\\|]');
console.log(splitPattern.split('apple,banana|orange'));
// 输出: ['apple', 'banana', 'orange']

Express.js 集成示例

以下是一个在 Express.js 应用中使用 Regolith 的完整示例:

import express from "express";
import { Regolith } from "@regolithjs/regolith";

const app = express();
const port = 3000;

// 创建 Regolith 正则模式
const intPattern = new Regolith("^\\d+$");
const floatPattern = new Regolith("^\\d*\\.\\d+$");

app.get("/check", (req, res) => {
    const value = req.query.value;

    if (!value) {
        return res.status(400).send("请提供 value 查询参数");
    }

    // 使用 Regolith 模式进行测试
    const isInt = intPattern.test(value);
    const isFloat = floatPattern.test(value);

    res.json({
        value,
        isInt,
        isFloat,
    });
});

app.listen(port, () => {
    console.log(`示例应用监听端口 ${port}`);
});

这个简单的 API 端点可以检查输入值是整数还是浮点数,而且完全免疫 ReDoS 攻击。

开发与构建

如果你想参与 Regolith 的开发或者自己构建这个库,以下是需要的步骤:

前置要求

  1. 需要安装 yarn包管理器
  2. 需要安装 Rust 工具链(通过 rustup)

构建过程

# 克隆项目
git clone https://github.com/JakeRoggenbuck/regolith.git

# 安装依赖
yarn install

# 构建项目
yarn build

构建过程会编译 Rust 代码并生成必要的绑定文件。

测试

Regolith 包含完整的测试套件:

# 测试 TypeScript/JavaScript 库
yarn test

# 测试 Rust 绑定
cargo test

当前有 93 个测试用例确保库的稳定性和正确性。

平台兼容性

Regolith 已经过多种平台的测试,包括:

平台 状态
Arm 64 Apple Darwin 正常工作
Arm 64 Linux GNU 正常工作
x86-64 Linux MUSL 正常工作
i686 PC Windows MSVC 正常工作
RISC-V 64 GC Linux GNU 正常工作

当前限制与未来规划

浏览器兼容性

目前 Regolith 主要专注于服务器端 JavaScript 和 TypeScript 应用,因为服务器是 ReDoS 攻击的主要目标。在客户端使用(如 React 的 “use client”)还存在一些挑战,主要是本地库的链接问题。

团队正在积极解决这一问题,可能通过 WebAssembly 或自研正则表达式引擎实现。这一进展可通过issue #40跟踪。

功能权衡

由于选择了保证线性时间复杂度的设计,Regolith 不支持某些高级正则表达式特性:

  • 回溯引用(backreferences)
  • 环视(look-around)操作

这一权衡在 Rust 社区已经被证明是值得的,大多数应用场景并不需要这些高级特性,而安全性和性能的收益是实实在在的。

常见问题解答

Regolith 与标准 RegExp 完全兼容吗?

Regolith 试图成为标准 RegExp 的直接替代品,但有两个重要区别:一是它不支持回溯引用和环视操作;二是它保证了线性时间复杂度,免疫 ReDoS 攻击。对于大多数使用场景,迁移到 Regolith 不需要修改代码。

为什么选择 Rust 而不是其他语言?

Rust 的正则表达式库已经经过广泛验证和优化,提供了卓越的性能和安全性。通过 napi-rs 创建绑定,可以让 JavaScript 生态享受到这些优势,而无需重新发明轮子。

Regolith 会影响性能吗?

在绝大多数情况下,Regolith 的性能与标准 RegExp 相当甚至更好。只有在处理某些复杂模式时,由于不支持一些高级特性,可能会有功能上的限制,但这换来的是安全性的极大提升。

如何报告 bug 或贡献代码?

如果发现 bug,可以通过电子邮件发送至 bug@jr0.org 或在 GitHub 上提交 issue。欢迎社区贡献代码和改进建议。

项目名称的由来

Regolith 这个名字来源于地质学,指的是行星表面由尘埃和岩石组成的顶层。作者在寻找以”reg”开头的单词时想到了这个术语, likely 是在学习恐龙相关课程时熟悉了这个词。

动机与背景

Regolith 的创作灵感来源于对为什么某些语言容易受到 ReDoS 攻击而其他语言不会的本科研究。作者发现一个问题:”为什么像 TypeScript、JavaScript 和 Python 这样的语言,最流行的正则表达式库不是线性时间引擎?”

JavaScript 生态中确实存在一些解决这一问题的库,如封装了 Google RE2 库的 re2js 和 node-re2,但它们要么API不同,要么采用率不高。绝大多数项目仍然使用默认的正则表达式引擎,这使得它们容易受到 ReDoS 攻击。

Regolith 的目标就是提供一个直接替代方案,让开发者无需担心 ReDoS 攻击,同时尽可能减少迁移成本。

总结

Regolith 代表了一种务实的安全方法:通过使用经过验证的技术(Rust 的正则表达式引擎)和简单的直接替代API,为 JavaScript 和 TypeScript 生态系统提供对 ReDoS 攻击的免疫力。

虽然它需要权衡一些高级功能,但对于绝大多数应用场景来说,这种权衡是值得的。随着网络安全威胁日益复杂,采用像 Regolith 这样从根本上设计安全的工具,将是构建 resilient 系统的关键一步。

无论你是维护一个被数百万项目使用的大型库,还是只是编写一个简单的表单验证,Regolith 都能帮助你避免潜在的服务中断和安全事件,让你能更专注于构建功能而非担心安全漏洞。