Uh oh!
There was an error while loading. Please reload this page.
fix(utils): fix polynomial backtracking in ansiRegex - #40762
Conversation
The ansiRegex used by stripAnsiEscapes has O(n^2) backtracking
on inputs like ESC[ followed by many semicolons. The root cause
is that semicolons can be matched by both the initial character
class [[\]()#;?]* and the parameter group (?:;...)*. Remove
semicolons from the initial group and relax \d{1,4} to \d{0,4}
so that empty parameters (e.g. ESC[;H) still parse correctly.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>Pavel Feldman (pavelfeldman)
commented
May 10, 2026
Note: Security framing is overstated, but doesn't undermine the fix. All three "attack vectors" require an attacker who can already inject arbitrary stdout/stderr content into a test run — which means they're already executing code in the test sandbox. The HTML-report scenario (renderer freezes for a reviewer's tab) is the most plausible, but it still presumes an attacker who controls test output. CVE-2021-3807 had higher impact because ansi-regex is a transitive dependency of huge swaths of the npm ecosystem; that doesn't transfer cleanly here. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| const start = performance.now(); | ||
| const result = stripAnsiEscapes(payload); | ||
| const elapsed = performance.now() - start; | ||
| expect(elapsed).toBeLessThan(100); |
There was a problem hiding this comment.
This will never fly on the bots, can't compare time on CI.
Sebastien Tardif (SebTardif)
commented
May 11, 2026
Removed the timing assertion. The test still verifies the regex doesn't hang - if polynomial backtracking were present, the test would timeout rather than fail a timing check. |
Test results for "MCP"5 failed 7052 passed, 1068 skipped Merge workflow run. |
Test results for "tests 1"2 flaky41746 passed, 850 skipped Merge workflow run. |
e8fa99c
into
microsoft:mainUh oh!
There was an error while loading. Please reload this page.
Summary
The
ansiRegexused bystripAnsiEscapeshas O(n^2) backtracking on crafted input. A payload of\x1b[+ 50,000 semicolons takes 3.1 seconds; 100,000 semicolons takes 12.6 seconds.This is the same class of vulnerability as CVE-2021-3807 (CVSS 7.5) in the
ansi-regexnpm package, fixed in chalk/ansi-regex#37. Playwright's regex is a hand-rolled copy of the same pattern.Root cause
The character class
[[\]()#;?]*includes;, and the subsequent parameter group(?:;[-a-zA-Z\d\/#&.:=?%@~_]*)*also starts with;. When the overall match fails, the regex engine tries all possible ways to split runs of semicolons between the two groups, producing O(n^2) backtracking.Attack vectors
Shared HTML test reports (browser DoS): A test outputs the crafted payload to stderr. CI generates an HTML report. When a reviewer opens the report and clicks on the failing test,
stripAnsiEscapesruns in the browser on the stdout/stderr body (testResultView.tsx), freezing the tab for 12+ seconds.CI reporter DoS: The JUnit and GitHub reporters call
stripAnsiEscapeson error messages and stdout/stderr during report generation. A malicious test freezes the reporter process, blocking CI.MCP server DoS: The MCP test streams process test output through
stripAnsiEscapes(streams.ts). A crafted test output freezes the MCP server.Fix
Remove
;from[[\]()#;?]*(changed to[[\]()#?]*) to eliminate the ambiguity. Relax\d{1,4}to\d{0,4}so that empty parameters likeESC[;H(cursor home with default params) still parse correctly. Also fixed the duplicate regex intests/config/utils.ts.Verification
\x1b[31mred\x1b[0mredred\x1b[;H\x1b[38;2;255;0;0mOrigin
The regex was introduced in #3575 (2020-08-22). The overlapping character classes have been present since the original commit.