Skip to content

fix(utils): ReDoS (cubic backtracking) in _strip_json_code_fence - #7082

Open
thinhnallf wants to merge 1 commit into
google:mainfrom
thinhnallf:fix-redos-strip-json-code-fence
Open

fix(utils): ReDoS (cubic backtracking) in _strip_json_code_fence#7082
thinhnallf wants to merge 1 commit into
google:mainfrom
thinhnallf:fix-redos-strip-json-code-fence

Conversation

@thinhnallf

Copy link
Copy Markdown

Summary

_strip_json_code_fence in src/google/adk/utils/_schema_utils.py uses a regex
re.fullmatch(r"```\w*\s*(.*?)\s*```", stripped, re.DOTALL) that exhibits
catastrophic backtracking (ReDoS), cubic O(n^3). It runs on
model-generated text before schema validation whenever output_schema is set:

  • agents/llm_agent.py (validate_schema(self.output_schema, result))
  • tools/agent_tool.py (validate_schema(output_schema, merged_text))
  • workflow/_llm_agent_wrapper.py (validate_schema(agent.output_schema, text))

No length cap precedes the regex (only an empty-string check).

Root cause

The overlapping whitespace quantifiers — leading \s*, the DOTALL-lazy (.*?)
(which also matches whitespace), and trailing \s* — create an ambiguous
partition of a whitespace run when the closing ``` fence is absent.
re.fullmatch backtracks over O(n^3) partitions before failing.

Impact

Python re is C-level and holds the GIL for the whole match, so one crafted
output hangs the entire asyncio event loop (all concurrent requests). Realistic
trigger: indirect prompt injection makes the model emit an unclosed ```json
fence with a whitespace body + one non-whitespace char.

Reproduction (measured)

whitespace n vulnerable time
1000 ~0.6 s
2000 ~4.6 s
4000 ~35 s

~7.5x per doubling ⇒ O(n^3). ~8 KB of output hangs for minutes.

Fix

Replace the regex with linear string operations plus one anchored,
non-backtracking regex for the optional language tag. Behaviour is identical on
all well-formed inputs; the attack input drops from ~35 s (n=4000) to
sub-millisecond.

…de_fence

The fence-stripping regex backtracks in O(n^3) on an unclosed code fence whose
body is whitespace. It runs on model output before schema validation
(llm_agent, agent_tool, workflow wrapper), so a large model response can hang
the event loop for all concurrent requests. Replace with linear string ops.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants