Skip to content

ReDoS in chat parser role-boundary regex (Python, TypeScript) #446

Closed

Description

The role-boundary regex in the Prompty chat parser is vulnerable to catastrophic backtracking (ReDoS). I found and fixed this in the Java runtime while building it (PR #444, commit f63b98b3); the same pattern is present and exploitable in Python and TypeScript. Filing separately rather than widening that PR.

Affected

Runtime Location Status
Java runtime/java/.../parsers/PromptyChatParser.java:53 Fixed in #444
Python runtime/python/prompty/prompty/parsers/prompty.py:33 Vulnerable
TypeScript runtime/typescript/packages/core/src/parsers/prompty.ts:26 Vulnerable
Rust runtime/rust/prompty/src/parsers/prompty.rs:31 Safe — the regex crate is linear-time and does not backtrack
C# / Go — No chat parser implemented; no exposure

Root cause

(\[((\w+\s*=\s*"?[^"]*"?\s*,?\s*)+)\])?

The value class [^"]* also matches the separator ,, whitespace, and the closing ]. Combined with the optional "? delimiters, one iteration of the inner group can consume text that a later iteration could equally have consumed, so the outer + has an exponential number of ways to partition the same input. Any input that ultimately fails to match forces the engine to explore all of them.

Note that a quoted attack string is not slow — the quotes anchor [^"]* and defuse the ambiguity. The trigger is unquoted attributes, which the pattern explicitly permits via "?.

Reproduction

Input "user[" + "a=b," * n + "!" (unquoted attributes, no closing ]), matched against each runtime's own compiled pattern:

reps Python TypeScript
12 72 ms 9 ms
14 653 ms 78 ms
16 5,889 ms 709 ms
18 53,352 ms 6,426 ms
20 (aborted) 58,240 ms

Roughly 9x per two repetitions. A ~30-character input hangs the process indefinitely.

Reachability

The parser runs over rendered prompt text, so any template that interpolates untrusted content into a position where a line can begin — a user message, retrieved document, or tool result — can carry the payload. It needs no valid role marker to be effective, because the cost is incurred on the failing match.

Fix

Make the value class exclude the separator and the closing bracket so no ambiguous split exists:

(\[(?:\w+\s*=\s*(?:"[^"]*"|[^",\]]*)\s*,?\s*)+\])?

An alternation of "a quoted string" or "an unquoted value containing neither , nor ]". I verified this against both engines — linear, and functionally identical on system:, # assistant:, user[name="Alice"]:, user[name=Bob, id=7]:, and the negative case notarole::

reps Python (fixed) TypeScript (fixed)
18-24 0.01 ms 0.05-0.12 ms
200 0.06 ms 0.01 ms
2000 1.55 ms 0.16 ms

Java additionally uses possessive quantifiers (\w++, \s*+, [^",\]]*+) as belt-and-braces. Python supports these from 3.11, which is Prompty's floor, so the Python fix can match Java exactly; JS has no possessive quantifiers, but the alternation alone is sufficient — there is nothing left to backtrack into.

Each fix should land with a regression test asserting a bounded runtime on a ~20-repetition adversarial input.

Activity

  1. added a commit that references this issue on Aug 18, 2026
    c9299f1
  2. sethjuarez commented on Aug 18, 2026

    @sethjuarez
    MemberAuthor

    Reopening: #457 fixed only the Python half. The TypeScript parser (
    untime/typescript/packages/core/src/parsers/prompty.ts) still uses the vulnerable role-boundary regex. Tracking here until the TS fix lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions