fix(a2a-core): eliminate ReDoS in Message code-block parsing#9374
fix(a2a-core): eliminate ReDoS in Message code-block parsing#9374rllyy97 wants to merge 2 commits into
Conversation
Replace the two lazy fenced-code-block regexes in Message.tsx with a linear String.indexOf scanner. The regexes ran in polynomial time (js/polynomial-redos, code scanning alerts 86 and 87) on untrusted message content containing many unterminated code fences. Add extractFencedCodeBlocks() plus unit tests covering extraction parity and a ReDoS guard for the pathological input CodeQL flagged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🤖 AI PR Validation ReportPR Review ResultsThank you for your submission! Here's detailed feedback on your PR title and body compliance:✅ PR Title
✅ Commit Type
✅ Risk Level
✅ What & Why
✅ Impact of Change
✅ Test Plan
✅ Contributors
✅ Screenshots/Videos
Summary Table
This PR passes review for title and body compliance.Last updated: Tue, 07 Jul 2026 20:31:46 GMT |
📊 Coverage CheckThe following changed files need attention:
Please add tests for the uncovered files before merging. |
There was a problem hiding this comment.
Pull request overview
This PR addresses CodeQL js/polynomial-redos findings in the chat Message renderer by replacing lazy fenced-code-block regex parsing on untrusted message.content with a linear-time scanner.
Changes:
- Added
extractFencedCodeBlocks(content)to parse fenced code blocks viaString.indexOfin a single pass. - Updated artifact download and markdown rendering paths to use the new extractor instead of regexes.
- Added unit tests for the extractor, including a pathological-input guard to prevent regression to polynomial-time behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| libs/a2a-core/src/react/components/Message/Message.tsx | Replaces regex-based fenced code block extraction with a linear scan helper and updates call sites. |
| libs/a2a-core/src/react/components/Message/tests/Message.codeblock.test.tsx | Adds extractor-focused unit tests and a ReDoS regression guard test. |
| const elapsed = performance.now() - start; | ||
|
|
||
| expect(blocks).toEqual([]); | ||
| expect(elapsed).toBeLessThan(1000); |
There was a problem hiding this comment.
Good catch — fixed in f5bb10e. I removed the absolute wall-clock assertion (elapsed < 1000ms) and replaced it with a machine-speed-independent scaling-ratio check.
The guard now times a 10x-larger pathological input and asserts largeTime / smallTime stays below a generous 30x ceiling. A linear scan grows ~10x with 10x input; the old O(n²) regex would grow ~100x. Asserting the ratio rather than absolute time keeps the test stable on slow/contended CI runners while still catching polynomial blow-up. It also warms up the JIT and averages over 20 iterations to reduce noise.
Ran it 3x locally — all green (14/14 each run).
…akiness Replace the absolute wall-clock assertion (elapsed < 1000ms) in the ReDoS regression guard with a machine-speed-independent scaling-ratio check. The test now compares per-call time for a 10x larger pathological input and asserts the ratio stays well below quadratic growth, so it stays green on slow or contended CI runners while still catching polynomial blow-up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Commit Type
Risk Level
What & Why
Fixes two
js/polynomial-redoscode scanning alerts (#86 and #87) inlibs/a2a-core/src/react/components/Message/Message.tsx.Both alerts flagged lazy fenced-code-block regexes applied to untrusted
message.content:handleDownload:/```[\w]*\n([\s\S]*?)\n```/processedContent:/```(\w*)\n([\s\S]*?)\n```/gThe lazy
([\s\S]*?)body scans to the end of the string from every```start position when the closing fence is missing. For input that starts with```\nand repeats```\namany times, this is O(n²) — a denial-of-service risk on agent/library-supplied content.The fix introduces a single module-level
extractFencedCodeBlocks(content)helper that finds fenced code blocks in one linear pass usingString.indexOf(no backtracking regex), faithfully reproducing the original matching semantics (opening fence + word-character info string +\n, code up to the first closing\n+```, and identical handling of unterminated / non-word-language fences). Both call sites now use the helper; behavior is unchanged for all legitimate inputs and the quadratic worst case is gone.Impact of Change
extractFencedCodeBlocks()helper in the Message module (exported for unit testing). No public API change to theMessagecomponent.Test Plan
pnpm --filter @microsoft/logic-apps-chat exec vitest run src/react/components/Message→ 141 passed (3 skipped);pnpm --filter @microsoft/logic-apps-chat run typecheck→ clean; no new Biome/ESLint diagnostics vs.mainAdded direct unit tests for
extractFencedCodeBlockscovering single/multiple blocks, no-language blocks, unterminated fences, non-word info strings, lazy-first-close parity, and a ReDoS guard that runs the exact CodeQL-flagged pathological input (200k fence repetitions) and asserts it completes in linear time.Contributors
Co-authored-by: Copilot
Screenshots/Videos
N/A — no visual changes.