@iris-eval/mcp-server
Advanced tools
Sorry, the diff of this file is too big to display
| /** | ||
| * Returns a human-readable reason when `source` shows superlinear | ||
| * backtracking, or null when it looks safe to deploy. | ||
| */ | ||
| export declare function regexBacktrackingBudgetExceeded(source: string, flags?: string): string | null; |
| /* | ||
| * Empirical backtracking probe for user-supplied regex patterns. | ||
| * | ||
| * safe-regex2 is a STATIC heuristic built on star height — it catches | ||
| * EXPONENTIAL blowup like `(a+)+$` and nothing else. Polynomial patterns | ||
| * sail through it: `a*a*a*a*a*b` is judged safe, and takes 156ms on 40 | ||
| * characters, 237ms on 60, and effectively forever on a realistic agent | ||
| * output. Deployed rules are re-registered into the engine at every | ||
| * startup, so a pattern like that keeps wedging the server after a | ||
| * restart — a permanent, self-inflicted denial of service. | ||
| * | ||
| * Static analysis of backtracking is hard; actually running the pattern is | ||
| * not. This measures it against short adversarial payloads and rejects | ||
| * anything already slow at trivial sizes. | ||
| * | ||
| * The catch-22 — running an untrusted regex to find out whether it hangs — | ||
| * is handled by escalating from a tiny payload upward and bailing the | ||
| * moment the budget is exceeded. A superlinear pattern blows past it at 16 | ||
| * or 32 characters, which is cheap; a linear one stays near zero even at | ||
| * 128. Nothing here ever runs a pattern against a large input. | ||
| */ | ||
| /** Total wall-clock a candidate pattern may spend across all probes. */ | ||
| const BUDGET_MS = 50; | ||
| const PROBE_SIZES = [16, 32, 64, 128]; | ||
| /** | ||
| * Characters that tend to maximise backtracking pressure for a given | ||
| * pattern: the literals it mentions, plus generic filler. Feeding a | ||
| * pattern its own alphabet is what makes the engine explore alternatives | ||
| * rather than fail at the first character. | ||
| */ | ||
| function probeAlphabets(source) { | ||
| const literals = source.replace(/[^A-Za-z0-9 ._@-]/g, ''); | ||
| const fromPattern = [...new Set(literals)].join('').slice(0, 4); | ||
| const alphabets = ['a', ' ', 'a.', 'ab']; | ||
| if (fromPattern.length > 0) | ||
| alphabets.unshift(fromPattern); | ||
| return alphabets; | ||
| } | ||
| /** | ||
| * Returns a human-readable reason when `source` shows superlinear | ||
| * backtracking, or null when it looks safe to deploy. | ||
| */ | ||
| export function regexBacktrackingBudgetExceeded(source, flags = '') { | ||
| let compiled; | ||
| try { | ||
| compiled = new RegExp(source, flags); | ||
| } | ||
| catch { | ||
| // Syntax is validated separately and reported with a better message. | ||
| return null; | ||
| } | ||
| const started = Date.now(); | ||
| for (const size of PROBE_SIZES) { | ||
| for (const alphabet of probeAlphabets(source)) { | ||
| const payload = alphabet.repeat(Math.ceil(size / alphabet.length)).slice(0, size) + ' |