[{"data":1,"prerenderedAt":173},["ShallowReactive",2],{"article-regex-redos-cpu-100-percent-vulnerability":3},{"article":4,"relatedArticles":130},{"slug":5,"category":6,"date":7,"readingMinutes":8,"tags":9,"relatedTool":15,"cn":16,"en":65,"author":114,"reviewedBy":117,"reviewedDate":119,"updatedDate":119,"sources":120,"noIndex":129},"regex-redos-cpu-100-percent-vulnerability","踩坑避坑","2026-07-28",7,[10,11,12,13,14],"ReDoS","Regex","Performance","CPU 100%","Security","/tools/dev/regex",{"title":17,"description":18,"intro":19,"sections":20},"小心 ReDoS 攻击！写错正则表达式导致 CPU 100% 爆表的原因与防范","剖析正则表达式拒绝服务攻击 (ReDoS) 底层机制，解释 NFA 引擎灾难性回溯 (Catastrophic Backtracking) 原理，提供安全正则改写与超时隔离防护手段。","在服务端或前端校验用户输入时，一条写错的正则表达式（例如带有嵌套量词的 `(a+)+$`）遭遇精心构造的恶意长文本时，会导致 CPU 占用率瞬间飙升至 100%，阻塞 Node.js 事件循环并引发整站瘫痪。本文解析 ReDoS 攻击原理与防御方案。",[21,25,30,34,38,43,47,51,55,61],{"heading":22,"paragraphs":23},"一、问题概述：灾难性回溯与 ReDoS 拒绝服务漏洞",[24],"传统正则表达式引擎（如 V8、Python、Java 默认的 NFA 引擎）采用回溯机制搜索匹配路径。当正则表达式中包含重叠或嵌套的量词（例如 `(a+)+$`、`(a|a)+$`）且目标输入文本在末尾匹配失败时，NFA 引擎会尝试指数级数量的可能组合（时间复杂度达到 $O(2^N)$ 或 $O(N^k)$），导致单次匹配耗时数分钟甚至数小时，彻底卡死单线程事件循环。",{"heading":26,"paragraphs":27,"code":29},"二、最小复现：导致 CPU 100% 爆表的嵌套量词代码",[28],"下面的例子示范了触发灾难性回溯的经典 ReDoS 模式以及 CPU 爆表效果：","/* 经典 ReDoS 触发模式: 嵌套量词 (a+)+$ */\n\nconst unsafeRegex = /^(a+)+$/;\nconst maliciousInput = \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!\"; // 28 个 'a' 加一个无法匹配的字符 '!'\n\nconsole.time(\"ReDoS Test\");\n// 下面这行代码会导致 V8 引擎产生上亿次无效回溯，严重阻塞 CPU！\nconst isMatched = unsafeRegex.test(maliciousInput);\nconsole.timeEnd(\"ReDoS Test\");",{"heading":31,"paragraphs":32},"三、根因分析：回溯决策树与非确定性路径搜索",[33],"1. **分支试错**：回溯型正则引擎面对 `(a+)+$` 时，每个字符 `a` 都可被分配给不同层级的重复。2. **指数级组合**：对于长度为 $N$ 的连续 `a`，失败后可能探索接近 $2^{N-1}$ 条分割路径。3. **事件循环饥饿**：Node.js 在事件循环线程同步执行这类匹配时，其他请求无法及时被调度；这属于线程被占用，不是数据库意义上的“死锁”。",{"heading":35,"paragraphs":36},"四、推荐方案：正则安全改写、输入长度限制与 Worker 超时隔离",[37],"1. 重构歧义嵌套和重叠分支。2. 在匹配前按业务契约限制输入长度。3. 不可信或动态正则放进可终止的 Web Worker / Node.js `worker_threads`。4. 高风险路径评估 RE2 类线性时间引擎，但先确认其受限语法能覆盖现有模式。静态扫描和单一长度阈值都只能作为纵深防御。",{"heading":39,"paragraphs":40,"code":42},"五、完整代码：真正可中断的浏览器 Worker 正则匹配",[41],"同步匹配完成后再记录耗时无法中断灾难性回溯。下面的浏览器代码把匹配放进独立 Worker，并在超时时直接终止 Worker；输入长度上限仍是第一道防线。Node.js 服务端应使用 `worker_threads` 或 RE2 类线性时间实现。","function regexMatchWithTimeout(\n  pattern: RegExp,\n  input: string,\n  maxLength = 500,\n  timeoutMs = 100,\n): Promise\u003Cboolean> {\n  if (input.length > maxLength) {\n    return Promise.reject(new RangeError(\"输入文本超过安全长度上限\"));\n  }\n\n  const source = [\n    \"self.onmessage = (event) => {\",\n    \"  const { source, flags, input } = event.data;\",\n    \"  try {\",\n    \"    self.postMessage({ matched: new RegExp(source, flags).test(input) });\",\n    \"  } catch (error) {\",\n    \"    self.postMessage({ error: error instanceof Error ? error.message : String(error) });\",\n    \"  }\",\n    \"};\",\n  ].join(\"\\n\");\n  const url = URL.createObjectURL(new Blob([source], { type: \"text/javascript\" }));\n  const worker = new Worker(url);\n\n  return new Promise((resolve, reject) => {\n    let timer = 0;\n    const cleanup = () => {\n      window.clearTimeout(timer);\n      worker.terminate();\n      URL.revokeObjectURL(url);\n    };\n\n    timer = window.setTimeout(() => {\n      cleanup();\n      reject(new Error(\"正则匹配超时，Worker 已终止\"));\n    }, timeoutMs);\n    worker.onmessage = (event) => {\n      cleanup();\n      if (event.data.error) reject(new Error(event.data.error));\n      else resolve(Boolean(event.data.matched));\n    };\n    worker.onerror = (event) => {\n      cleanup();\n      reject(new Error(event.message || \"正则 Worker 执行失败\"));\n    };\n    worker.postMessage({ source: pattern.source, flags: pattern.flags, input });\n  });\n}\n\nconsole.log(await regexMatchWithTimeout(/^a+$/, \"aaaaaaaa!\")); // false",{"heading":44,"paragraphs":45},"六、常见错误方案",[46],"误以为懒惰量词 `+?` 能彻底解决 ReDoS（懒惰量词在末尾字符匹配失败时依然会进行全力回溯）；在没有输入长度限制的情况下向服务端暴露自定义用户正则校验功能。",{"heading":48,"paragraphs":49},"七、边界条件：引擎能力、CSP 与 Worker 成本",[50],"V8 与 Node.js 原生 RegExp 支持反向引用和环视等高级能力，部分模式会出现灾难性回溯。Google RE2 通过有限自动机策略和受限语法换取与输入长度线性相关的执行时间。浏览器 Blob Worker 还受站点 `worker-src` CSP 控制；高频短匹配不宜每次创建 Worker，可复用受控 Worker 池，但超时后必须丢弃被卡住的实例。",{"heading":52,"paragraphs":53},"八、如何检测与评估正则表达式安全风险",[54],"使用在线 Safe Regex 工具扫描模式中是否存在 `(a+)+` 或 `(a|b)+` 结构；在 CI/CD 中加入 ReDoS 模糊测试 (Fuzzing)；在监控系统中跟踪 RegExp 相关的 CPU 线程消耗。",{"heading":56,"paragraphs":57},"九、FAQ",[58,59,60],"问：为什么在 Node.js 中一条 ReDoS 正则能带崩整个服务？答：因为 Node.js 是单线程事件循环架构，同步的 CPU 密集回溯计算会占用整个 CPU 核心，导致后续所有 HTTP 请求无法被事件循环调度。","问：所有正则表达式都存在 ReDoS 风险吗？答：不是。只有包含了“重叠量词”或“歧义交叠分支”且作用于无法匹配的末尾文本时，才会触发灾难性回溯。","问：如何修改 `(a+)+$` 使其变得安全？答：直接改写为单层量词 `^a+$`，完全消除了分配路径歧义，执行时间缩短为毫秒级。",{"heading":62,"paragraphs":63},"十、总结",[64],"ReDoS 攻击是利用正则引擎物理算法缺陷的拒绝服务漏洞。工程实践中应避免嵌套重叠量词、严格校验输入文本长度，并在高危场景下引入 Worker 超时隔离或 DFA 引擎。",{"title":66,"description":67,"intro":68,"sections":69},"Preventing ReDoS Attacks: How Catastrophic Backtracking Causes 100% CPU Spikes","Examine Regular Expression Denial of Service (ReDoS) mechanics, detailing NFA engine catastrophic backtracking and offering safe rewriting & timeout safeguards.","Validating untrusted user inputs with flawed regular expressions (such as nested quantifiers like `(a+)+$`) can trigger catastrophic backtracking. A single malicious string can spike CPU usage to 100%, locking the Event Loop. This article explores ReDoS mechanisms and defense strategies.",[70,74,79,83,87,92,96,100,104,110],{"heading":71,"paragraphs":72},"1. Problem: Catastrophic Backtracking and ReDoS vulnerabilities",[73],"Traditional regular expression engines (such as NFA engines in V8, Python, and Java) rely on backtracking. When a pattern contains overlapping or nested quantifiers (e.g., `(a+)+$`, `(a|a)+$`) and evaluates a non-matching input, the engine tests an exponential number of paths ($O(2^N)$ or $O(N^k)$), consuming 100% CPU and freezing single-threaded applications.",{"heading":75,"paragraphs":76,"code":78},"2. Minimal reproduction: nested quantifier code triggering 100% CPU spikes",[77],"This example demonstrates a classic ReDoS pattern that induces catastrophic backtracking:","/* Classic ReDoS trigger: nested quantifiers (a+)+$ */\n\nconst unsafeRegex = /^(a+)+$/;\nconst maliciousInput = \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!\"; // 28 'a' chars plus non-matching '!'\n\nconsole.time(\"ReDoS Test\");\n// The line below causes V8 to evaluate hundreds of millions of failing backtracking paths!\nconst isMatched = unsafeRegex.test(maliciousInput);\nconsole.timeEnd(\"ReDoS Test\");",{"heading":80,"paragraphs":81},"3. Root cause: backtracking decision trees and ambiguous paths",[82],"1. **Branch trialing**: with `(a+)+$`, a backtracking engine can allocate each `a` across different repetitions. 2. **Exponential paths**: a failing suffix may force exploration of roughly $2^{N-1}$ partitions. 3. **Event-loop starvation**: when Node.js performs this synchronous work on the event-loop thread, other requests cannot be scheduled promptly; the thread is occupied rather than deadlocked in the database sense.",{"heading":84,"paragraphs":85},"4. Recommendation: refactoring, input bounds, and worker isolation",[86],"Refactor ambiguous nesting and overlapping branches, bound input according to the business contract, and execute untrusted or dynamic regexes in a terminable Web Worker or Node.js `worker_threads`. Evaluate a linear-time engine such as RE2 for high-risk paths, after confirming its restricted syntax supports the required patterns. Static scanners and one length threshold are defense in depth, not proofs of safety.",{"heading":88,"paragraphs":89,"code":91},"5. Complete code: genuinely interruptible matching in a browser Worker",[90],"Recording elapsed time after a synchronous match cannot interrupt catastrophic backtracking. This browser implementation runs the match in an isolated Worker and terminates it on timeout, while retaining an input-length guard. On Node.js, use `worker_threads` or a linear-time engine such as RE2.","function regexMatchWithTimeout(\n  pattern: RegExp,\n  input: string,\n  maxLength = 500,\n  timeoutMs = 100,\n): Promise\u003Cboolean> {\n  if (input.length > maxLength) {\n    return Promise.reject(new RangeError(\"Input exceeds the safe length limit\"));\n  }\n\n  const source = [\n    \"self.onmessage = (event) => {\",\n    \"  const { source, flags, input } = event.data;\",\n    \"  try {\",\n    \"    self.postMessage({ matched: new RegExp(source, flags).test(input) });\",\n    \"  } catch (error) {\",\n    \"    self.postMessage({ error: error instanceof Error ? error.message : String(error) });\",\n    \"  }\",\n    \"};\",\n  ].join(\"\\n\");\n  const url = URL.createObjectURL(new Blob([source], { type: \"text/javascript\" }));\n  const worker = new Worker(url);\n\n  return new Promise((resolve, reject) => {\n    let timer = 0;\n    const cleanup = () => {\n      window.clearTimeout(timer);\n      worker.terminate();\n      URL.revokeObjectURL(url);\n    };\n\n    timer = window.setTimeout(() => {\n      cleanup();\n      reject(new Error(\"Regex timed out; Worker terminated\"));\n    }, timeoutMs);\n    worker.onmessage = (event) => {\n      cleanup();\n      if (event.data.error) reject(new Error(event.data.error));\n      else resolve(Boolean(event.data.matched));\n    };\n    worker.onerror = (event) => {\n      cleanup();\n      reject(new Error(event.message || \"Regex Worker failed\"));\n    };\n    worker.postMessage({ source: pattern.source, flags: pattern.flags, input });\n  });\n}\n\nconsole.log(await regexMatchWithTimeout(/^a+$/, \"aaaaaaaa!\")); // false",{"heading":93,"paragraphs":94},"6. Incorrect approaches",[95],"Assuming lazy quantifiers `+?` eliminate ReDoS (lazy quantifiers still backtrack exhaustively on failing suffixes). Exposing custom user-defined regexes to servers without string length limits.",{"heading":97,"paragraphs":98},"7. Boundaries: engine features, CSP, and Worker overhead",[99],"Native V8 and Node.js regexes support advanced features such as backreferences and lookarounds, and some patterns catastrophically backtrack. Google RE2 trades a restricted syntax for finite-automata execution whose time is linear in input length. Blob Workers also depend on the site's `worker-src` CSP. Reuse a controlled pool for frequent short matches, but discard any Worker that times out.",{"heading":101,"paragraphs":102},"8. Verification",[103],"Scan regex patterns for `(a+)+` or `(a|b)+` structures using static analysis tools. Incorporate ReDoS fuzz testing into CI/CD pipelines. Monitor CPU usage spikes tied to RegExp execution.",{"heading":105,"paragraphs":106},"9. FAQ",[107,108,109],"Why can a single ReDoS expression crash an entire Node.js server? Node.js uses a single-threaded Event Loop; synchronous CPU-bound backtracking starves the thread, preventing request scheduling.","Do all regular expressions have ReDoS risks? No. Only patterns with overlapping quantifiers or ambiguous branches evaluating non-matching input suffer from catastrophic backtracking.","How do I fix `(a+)+$` to make it safe? Simplify it to a single-level quantifier `^a+$`, removing partition ambiguity and reducing execution time to milliseconds.",{"heading":111,"paragraphs":112},"10. Summary",[113],"ReDoS attacks exploit algorithmic flaws in NFA regex engines. Production applications must eliminate nested quantifiers, enforce input length limits, and employ worker isolation or DFA engines for high-risk inputs.",{"name":115,"url":116},"Tiny's Tool Editorial Team","https://tinystool.org/about",{"name":118,"url":116},"Tiny's Tool Technical Review","2026-08-08",[121,125],{"title":122,"url":123,"publisher":124},"RE2 Syntax and Supported Features","https://github.com/google/re2/wiki/Syntax","Google RE2",{"title":126,"url":127,"publisher":128},"ECMAScript Language Specification","https://tc39.es/ecma262/","Ecma International",false,[131,146,159],{"slug":132,"noIndex":133,"category":134,"date":7,"updatedDate":119,"reviewedDate":119,"readingMinutes":135,"tags":136,"relatedTool":15,"cn":140,"en":143},"regex-greedy-vs-lazy-backtracking-performance",true,"最佳实践",6,[11,137,138,12,139],"Greedy","Lazy","Optimization",{"title":141,"description":142},"贪婪匹配与非贪婪匹配原理：如何写出高性能、无过度回溯的正则表达式","对比贪婪量词 (`*`, `+`) 与懒惰量词 (`*?`, `+?`) 的搜索机制，澄清“懒惰匹配不回溯”的误区，讲解基于精确字符类与锚点的高效正则优化方案。",{"title":144,"description":145},"Greedy vs Lazy Regex Quantifiers: Minimizing Backtracking and Optimizing Matching Performance","Compare greedy (`*`, `+`) and lazy (`*?`, `+?`) search mechanisms, debunking lazy non-backtracking myths and providing precise character class optimizations.",{"slug":147,"noIndex":133,"category":148,"date":7,"updatedDate":119,"reviewedDate":119,"readingMinutes":135,"tags":149,"relatedTool":15,"cn":153,"en":156},"regex-global-g-flag-lastindex-bug","错误排查",[11,150,151,152],"g Flag","lastIndex","Stateful Bug",{"title":154,"description":155},"JavaScript 正则全局匹配 /g 模式下反复调用 .test() 结果交替变化的怪异 Bug","剖析带有全局标志 `/g` 的 RegExp 实例维护内部 `lastIndex` 有状态属性引发的连续 `.test()` 出现 `true` -> `false` -> `true` 交替 Bug，提供状态重置与纯函数防范方案。",{"title":157,"description":158},"JavaScript RegExp /g Flag Stateful Bug: Why Repeated .test() Returns Alternating Booleans","Analyze how the global `/g` flag mutates `RegExp.lastIndex`, causing consecutive `.test()` calls to alternate between `true` and `false`.",{"slug":160,"noIndex":133,"category":134,"date":7,"updatedDate":119,"reviewedDate":119,"readingMinutes":161,"tags":162,"relatedTool":166,"cn":167,"en":170},"code-diff-large-file-web-worker-dom-chunks",8,[163,164,165,12],"Code Diff","Web Worker","Virtualization","/tools/dev/code-diff",{"title":168,"description":169},"万行大文件代码对比性能优化：Web Worker 异步计算与 DOM 虚拟化分片渲染","讲解在前端对比万行大文件时，如何使用 Web Worker 隔离 CPU 密集 Diff 计算，并结合 DOM 虚拟化视口 (Virtualization) 解决主线程卡死问题。",{"title":171,"description":172},"Optimizing Large File Code Diff Performance: Web Worker Computation and DOM Virtualization","Learn how to optimize web-based large file diffs by offloading CPU-bound tasks to Web Workers and applying DOM virtualization for zero UI lag.",1786725508472]