From b684673b24616195c1896b0027e8f882a5b0bc8d Mon Sep 17 00:00:00 2001 From: Psyborgs-git <49641518+Psyborgs-git@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:33:22 +0000 Subject: [PATCH] perf(terminal): cache regex compilations in validateInput hot path Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .jules/bolt.md | 3 +++ packages/terminal/src/permissions.ts | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..2c3b009 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - [Terminal Input Regex Recompilation Bottleneck] +**Learning:** Compiling regular expressions in a hot loop (like `validateInput` running for every incoming websocket chunk on terminal input) causes significant CPU overhead, especially as the number of policy patterns grows. +**Action:** Always pre-compile or cache `RegExp` objects on hot execution paths. Use bounded structures (like `LRU` cache or a bounded `Map` size check) if patterns can be dynamically generated, to prevent memory leaks from caching unbounded strings. \ No newline at end of file diff --git a/packages/terminal/src/permissions.ts b/packages/terminal/src/permissions.ts index 3755e8c..5f5e901 100644 --- a/packages/terminal/src/permissions.ts +++ b/packages/terminal/src/permissions.ts @@ -106,6 +106,10 @@ export function validateShell( return { allowed: true }; } +// Bounded cache for compiled regular expressions to avoid recompiling on every keystroke +const MAX_REGEX_CACHE_SIZE = 100; +const regexCache = new Map(); + /** * Check if input text contains blocked patterns. * This is a best-effort filter — not a security boundary. @@ -116,7 +120,17 @@ export function validateInput( ): AccessCheckResult { for (const pattern of policy.inputBlockPatterns) { try { - const re = new RegExp(pattern); + let re = regexCache.get(pattern); + if (!re) { + // Prevent memory leak if dynamic policies create unbounded patterns + if (regexCache.size >= MAX_REGEX_CACHE_SIZE) { + const firstKey = regexCache.keys().next().value; + if (firstKey !== undefined) regexCache.delete(firstKey); + } + re = new RegExp(pattern); + regexCache.set(pattern, re); + } + if (re.test(input)) { return { allowed: false,