005ReviewseniorJavaScript / Express Routing真实来源改编 path-to-regexp / Express
path-to-regexp:修复相邻路由参数的正则灾难回溯
审查一个声称修复 path-to-regexp ReDoS 公告的补丁:它给相邻参数(如 /:a-:b)的捕获组加了边界,消除长路径上的正则灾难回溯。
@@ -22,10 +22,17 @@ function escapeString(str: string): string { // Turn a single token into its regexp fragment. `next` is the following // token, if any. function tokenToRegExp(token: Token, next?: Token): string { if (token.type !== 'param') return escapeString(token.value) + // Adjacent params like /:a-:b compile to ([^/]+?)-([^/]+?); on a long+ // non-matching path the two lazy captures overlap across the '-' and+ // the engine backtracks catastrophically (GHSA-9wv6-86v2-598j).+ // Stop the earlier capture at the '-' separator so the two groups can+ // no longer overlap.+ if (token.suffix === '-' && next && next.type === 'param') {+ return '([^/-]+?)'+ }+ const capture = token.pattern || DEFAULT_PATTERN return '(' + capture + ')' }@@ -18,6 +18,17 @@ describe('route compilation', () => { const re = tokensToRegExp(parse('/:a')) expect(re.test('/users')).toBe(true) }) + it('does not hang on two dashed params', () => {+ const re = tokensToRegExp(parse('/:a-:b'))+ // Previously this input caused catastrophic backtracking.+ const input = '/' + 'a'.repeat(20000)+ const start = Date.now()+ expect(re.test(input)).toBe(false)+ expect(Date.now() - start).toBeLessThan(100)+ })+ it('matches a simple two-param route', () => { const re = tokensToRegExp(parse('/:a-:b')) expect(re.test('/foo-bar')).toBe(true) }) })