From 8330111c9318e7d2900722703753cfb6fd62b8dd Mon Sep 17 00:00:00 2001 From: NOXX - Commiter Date: Wed, 17 Jun 2026 23:25:58 +0300 Subject: [PATCH] perf(obfuscation): cache per-word regexes instead of recompiling every request (#4109) Integrated into release/v3.8.28 (r8) --- open-sse/services/antigravityObfuscation.ts | 16 +++++++++++++++- open-sse/services/claudeCodeObfuscation.ts | 18 ++++++++++++++++-- open-sse/services/systemTransforms.ts | 17 ++++++++++++++++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/open-sse/services/antigravityObfuscation.ts b/open-sse/services/antigravityObfuscation.ts index 63ebf998c7..adbaf3d58b 100644 --- a/open-sse/services/antigravityObfuscation.ts +++ b/open-sse/services/antigravityObfuscation.ts @@ -38,12 +38,26 @@ function escapeRegex(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } +// Per-word regex cache — avoids recompiling one RegExp per word on every request body. +// Bounded by distinct configured words; global regexes are safe to reuse (String.replace +// resets lastIndex). +const _obfuscationRegexCache = new Map(); +function getObfuscationRegex(word: string): RegExp { + let regex = _obfuscationRegexCache.get(word); + if (!regex) { + if (_obfuscationRegexCache.size > 2000) _obfuscationRegexCache.clear(); + regex = new RegExp(escapeRegex(word), "gi"); + _obfuscationRegexCache.set(word, regex); + } + return regex; +} + export function obfuscateSensitiveWords(text: string): string { if (!text || words.length === 0) return text; let result = text; for (const word of words) { if (!word) continue; - const regex = new RegExp(escapeRegex(word), "gi"); + const regex = getObfuscationRegex(word); result = result.replace(regex, (m) => (m.length <= 1 ? m : m[0] + ZWJ + m.slice(1))); } return result; diff --git a/open-sse/services/claudeCodeObfuscation.ts b/open-sse/services/claudeCodeObfuscation.ts index 864c2e07fc..3a8423cbf1 100644 --- a/open-sse/services/claudeCodeObfuscation.ts +++ b/open-sse/services/claudeCodeObfuscation.ts @@ -40,14 +40,28 @@ function obfuscateWord(word: string): string { return word[0] + ZWJ + word.slice(1); } +// Per-word regex cache — obfuscateSensitiveWords recompiles one RegExp per word on every +// request body otherwise. Bounded by distinct configured words; global regexes are safe to +// reuse because String.replace resets lastIndex. +const _obfuscationRegexCache = new Map(); +function getObfuscationRegex(word: string): RegExp { + let regex = _obfuscationRegexCache.get(word); + if (!regex) { + if (_obfuscationRegexCache.size > 2000) _obfuscationRegexCache.clear(); + regex = new RegExp(escapeRegex(word), "gi"); + _obfuscationRegexCache.set(word, regex); + } + return regex; +} + export function obfuscateSensitiveWords(text: string): string { if (!text || sensitiveWords.length === 0) return text; let result = text; for (const word of sensitiveWords) { if (!word) continue; - // Case-insensitive replacement - const regex = new RegExp(escapeRegex(word), "gi"); + // Case-insensitive replacement (cached: see getObfuscationRegex) + const regex = getObfuscationRegex(word); result = result.replace(regex, (match) => obfuscateWord(match)); } return result; diff --git a/open-sse/services/systemTransforms.ts b/open-sse/services/systemTransforms.ts index 665aa65546..a647a6c967 100644 --- a/open-sse/services/systemTransforms.ts +++ b/open-sse/services/systemTransforms.ts @@ -269,12 +269,27 @@ function obfuscateWord(word: string): string { * list instead of the module-level singleton, so concurrent requests with * different op configs do not race. */ +// Per-word regex cache: obfuscateWithList runs over the whole request body on every +// request when obfuscation is enabled, recompiling one RegExp per word each time. The +// word list is stable per op config, so memoize. Bounded by distinct configured words +// (with a defensive cap). Global regexes are safe to reuse: String.replace resets lastIndex. +const _obfuscationRegexCache = new Map(); +function getObfuscationRegex(word: string): RegExp { + let regex = _obfuscationRegexCache.get(word); + if (!regex) { + if (_obfuscationRegexCache.size > 2000) _obfuscationRegexCache.clear(); + regex = new RegExp(escapeRegex(word), "gi"); + _obfuscationRegexCache.set(word, regex); + } + return regex; +} + function obfuscateWithList(text: string, words: string[]): string { if (!text || words.length === 0) return text; let result = text; for (const word of words) { if (!word) continue; - const regex = new RegExp(escapeRegex(word), "gi"); + const regex = getObfuscationRegex(word); result = result.replace(regex, (match) => obfuscateWord(match)); } return result;