Skip to content

fix(react): remove ReDoS paths in data URL and toolbar test-id parsing#962

Open
jedrazb wants to merge 1 commit into
mainfrom
fix/codeql-react-redos
Open

fix(react): remove ReDoS paths in data URL and toolbar test-id parsing#962
jedrazb wants to merge 1 commit into
mainfrom
fix/codeql-react-redos

Conversation

@jedrazb

@jedrazb jedrazb commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Closes two high-severity CodeQL js/polynomial-redos alerts in the React adapter. Both regexes had many candidate start positions, each scanning to the end of the string, so a long crafted input ran in quadratic time.

  • InsertImageDialog.tsx dataUrlToBlob/:(.*?);/ is replaced by indexOf-based MIME extraction (no regex, linear, and slightly more accurate for types without a ;base64 marker).
  • Toolbar.tsx test-id helper — /\([^)]*\)/g becomes /\([^()]*\)/g, so the run cannot span unmatched ( and backtrack.

No Vue counterpart exists for either path (the Vue adapter has no dataUrlToBlob and uses static test ids), so this is correctly React-only.

Added dataUrlToBlob.test.ts covering MIME parsing, the image/png fallback, and a ReDoS guard that must finish near-instantly on adversarial input (it previously took ~37s).

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Parse the data URL MIME type with index math instead of a regex that
retried at every ':' and scanned to the end of the string, and bound
the toolbar test-id parenthesis strip to '[^()]' so it cannot backtrack
across unmatched '('. Both ran in quadratic time on crafted input.

Resolves CodeQL js/polynomial-redos in InsertImageDialog.tsx and
Toolbar.tsx.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docx-editor Ready Ready Preview, Comment Jun 20, 2026 7:46pm

Request Review

@eigenpal-release-pal

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅

Posted by the CLA bot.

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes two CodeQL js/polynomial-redos alerts in the React adapter by eliminating the backtracking patterns rather than suppressing the alerts. Both fixes are minimal and behaviorally equivalent for all well-formed inputs.

  • InsertImageDialog.tsx: dataUrlToBlob replaces /:(.*?);/ with indexOf-based MIME extraction; handles all standard data URL shapes correctly and is demonstrably O(n), with new unit tests including a ReDoS timing guard.
  • Toolbar.tsx: The test-id helper's character class is tightened from [^)] to [^()], preventing quadratic backtracking on parenthesis-heavy titles without changing output for normal input.

Confidence Score: 4/5

Safe to merge; both fixes are narrow, correct, and tested. No layout, paint, or PM-state paths are touched.

The MIME extraction logic is correct for all standard data URL shapes and demonstrably linear. The semi === -1 branch can admit an arbitrarily large string as the MIME candidate for adversarial input; a slash-presence guard would tighten this. The Toolbar regex fix is correct but lacks a unit test. Neither finding affects real-world behavior for legitimate image uploads.

InsertImageDialog.tsx — the semi === -1 branch silently accepts any string after the colon as a MIME type; worth a quick second look.

Important Files Changed

Filename Overview
packages/react/src/components/dialogs/InsertImageDialog.tsx Replaces the backtracking /:(.*?);/ regex with indexOf-based MIME extraction; logic is correct for all standard data URL shapes, though the semi === -1 branch admits an unbounded MIME candidate from adversarial input.
packages/react/src/components/Toolbar.tsx Single-character class fix from [^)] to [^()] correctly prevents quadratic backtracking on parenthesis-heavy titles; behavior for well-formed input is identical.
packages/react/src/components/dialogs/dataUrlToBlob.test.ts New test file covering MIME parsing, empty-MIME fallback, and a ReDoS timing guard; timing threshold of 1 000 ms is workable but generous for what should be a microsecond-scale operation.
.changeset/react-redos-hardening.md Correct patch-level changeset for @eigenpal/docx-editor-react; consumer-facing description is clear and concise.

Reviews (1): Last reviewed commit: "fix(react): remove ReDoS paths in data U..." | Re-trigger Greptile

Comment on lines +16 to +22
test('stays linear on an adversarial prefix (no ReDoS)', () => {
// A long `:a:a:a…` prefix with no `;` used to backtrack quadratically.
const evil = ':' + ':a'.repeat(200_000) + ',QUJD';
const start = performance.now();
dataUrlToBlob(evil);
expect(performance.now() - start).toBeLessThan(1_000);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Timing-bound ReDoS guard may be flaky on loaded CI

The 1 000 ms threshold is generous in isolation, but on a heavily loaded CI runner a GC pause or context switch during the atob/Blob path could push runtime above 1 s even for an O(n) implementation. It's also worth noting that the adversarial string has no semicolon, so mime is set to the entire 400 000-char candidate before Blob construction — the test proves the implementation is linear, but only indirectly. Consider asserting a tighter bound (e.g. 200 ms) or adding a sub-millisecond relative comparison to make the intent harder to miss.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +304 to 307
// `[^()]` (not `[^)]`) so the run can't span unmatched `(` and
// backtrack quadratically on a long parenthesis-heavy title.
.replace(/\([^()]*\)/g, '')
.trim();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No test coverage for the Toolbar regex fix

The dataUrlToBlob fix ships with a ReDoS timing guard and functional tests, but the [^()]* change has no corresponding test. A simple unit test that passes a parenthesis-heavy title string and asserts the expected test-id output (and optionally a timing guard for adversarial input like '('.repeat(50_000)) would close the coverage gap and make a future regression visible at the unit level rather than only through browser E2E tests.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +767 to +768
const candidate = header.slice(colon + 1, semi === -1 ? undefined : semi);
if (candidate) mime = candidate;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 When no semicolon is found (semi === -1), header.slice(colon + 1, undefined) returns the entire tail of parts[0] after the colon. For adversarial input with no semicolon and a very long colon-prefixed string, this silently sets a megabyte-scale string as the Blob type. Adding a basic sanity check — e.g. requiring a / in the candidate (all real MIME types have one) — would prevent a huge string from leaking into the Blob and make the fallback behavior more predictable for malformed input.

Suggested change
const candidate = header.slice(colon + 1, semi === -1 ? undefined : semi);
if (candidate) mime = candidate;
const candidate = header.slice(colon + 1, semi === -1 ? undefined : semi);
if (candidate && candidate.includes('/')) mime = candidate;

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.

1 participant