Fix quadratic ReDoS in skill-mention regexes

The extract (SKILL_MENTION_RE) and strip (strip_re) patterns in
middleware.py paired overlapping quantifiers (`[^|>]+\|?[^>]*`), which
backtrack in O(n^2) on a `<$` run that never closes with `>`. Both run
unconditionally on every chat completion (extract for persisted-chat
skill resolution, strip before the model sees content), so a single
message, RAG chunk, or tool output containing that shape pegs a CPU core
in re.sub and blocks the event loop. On the default single uvicorn
worker this freezes the whole instance for every user.

Rewrite the optional `|label` as a non-capturing optional group so the
two quantifiers no longer overlap, making both patterns linear. Capture
groups and output are unchanged on well-formed `<$id|label>`, `<$id|>`,
and bare `<$id>` mentions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Classic298 2026-06-06 18:21:48 +02:00
parent b1d40f3409
commit 1f575a252e

View file

@ -2220,7 +2220,8 @@ def process_messages_with_output(
return processed
SKILL_MENTION_RE = re.compile(r'<\$([^|>]+)\|?[^>]*>')
# non-capturing optional |label avoids quadratic ReDoS backtracking
SKILL_MENTION_RE = re.compile(r'<\$([^|>]+)(?:\|[^>]*)?>')
def _get_text_parts(message: dict) -> list[str]:
@ -2244,7 +2245,8 @@ def extract_skill_ids_from_messages(messages: list[dict]) -> set[str]:
def strip_skill_mentions(messages: list[dict]) -> None:
"""Replace <$skillId|label> mention tags with the label in message content in-place."""
strip_re = re.compile(r'<\$[^|>]+\|?([^>]*)>')
# non-capturing optional |label avoids quadratic ReDoS backtracking
strip_re = re.compile(r'<\$[^|>]+(?:\|([^>]*))?>')
for message in messages:
content = message.get('content')
if isinstance(content, str) and strip_re.search(content):