Skip to content

fix: harden large tab-chat image persistence - #2989

Merged
esokullu merged 2 commits into
webbrain-one:mainfrom
alectimison-maker:fix/tab-chat-large-image-persistence
Sep 7, 2026
Merged

fix: harden large tab-chat image persistence#2989
esokullu merged 2 commits into
webbrain-one:mainfrom
alectimison-maker:fix/tab-chat-large-image-persistence

Conversation

@alectimison-maker

Copy link
Copy Markdown
Contributor

Summary

  • Replace the large-payload regular expression in tab-chat image compaction with a linear scanner.
  • Preserve existing image data URL behavior, including MIME parameters and mixed-case markers.
  • Keep Chrome and Firefox implementations aligned and add a 6 MB regression case.

Context

The latest upstream main still contained the implementation discussed in PR #2982. The reported Maximum call stack size exceeded was intermittent: the original code passed two full runs and 20 isolated reproductions in this environment. This change removes the risky large-payload regex path and makes the reported 6 MB scenario explicit in the test suite.

Validation

  • npm test: 2202 passed, 0 failed
  • Security corpus: 60/60
  • Provider model limits: passed
  • Rich-text toolbar guard: 33 passed

@vercel

vercel Bot commented Sep 7, 2026

Copy link
Copy Markdown

@alectimison-maker is attempting to deploy a commit to the esokullu's projects Team on Vercel.

A member of the Team first needs to authorize it.

The rewritten scanner looked for `;base64,` from the end of the MIME type
onward, stopping only at a comma or the end of the string. The regex it
replaced required the metadata to start with `;`, so a bare `data:image/png`
mention in a transcript simply did not match. The scanner instead ran past
the quote and any markup after it until it found the next real image, and
the replacement deleted everything in between, storing malformed HTML that
was then restored into the DOM. Unmatched markers also scanned to the end of
the string each time, which is O(k*n) on the transcripts this code is meant
to speed up.

Require the parameter section to begin with `;` and consume each
`;name=value` with a media-type parameter character class, so a quote, an
angle bracket, whitespace, or a comma ends the scan and rejects the match.
Covered by a regression test on the bare-mention-then-payload case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ko4GhesjH6KESXozVNS735
@esokullu

esokullu commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Reviewed this and pushed one fix to the branch (f390456). The new scanner diverges from the old regex in a way that can delete transcript content.

imageDataPayloadEnd walked forward from data:image/<mime> hunting for ;base64,, stopping only at a comma or the end of the string. The old regex required the metadata section to start with ; right after the MIME type, so a bare data:image/png occurrence never matched at that position: someone typing the MIME type in a message, a code block that mentions it, an <img src="data:image/png"> with no payload. The scanner instead skips the quote, the >, whole tags, whatever is there, until it finds a real ;base64, later in the document. Everything from the first marker through that later payload is then replaced with the transparent pixel:

input:  <div>use data:image/png here</div><img src="data:image/gif;base64,AAAA">
old:    <div>use data:image/png here</div><img src="data:image/gif;base64,iVBOR...">
new:    <div>use data:image/png;base64,iVBOR...">

The </div> and the <img> tag are gone, and that malformed HTML is what gets restored into the DOM on the next tab chat restore.

The same missing bound works against the performance goal here: every unmatched data:image/ marker scans to the end of the string, so k of them in a 7 MB transcript cost O(k*n).

The fix requires the parameter section to begin with ; and consumes each ;name=value with a media type parameter character class. Any character that cannot belong to a parameter (a quote, an angle bracket, whitespace, a comma) ends the walk and rejects the match, so it can never run past the attribute it started in. Both the Chrome and Firefox copies were identical and both are patched.

Behavior now matches the old regex on everything I checked: bare mention then image, non base64 SVG, two normal images, mixed case with a charset parameter, empty payload, empty input, parameters containing _ and *. One difference is deliberate. data:image/png;charset=x"><b>hi</b><img src="data:image/jpeg;base64,... also swallowed the intervening markup under the old regex, and the bound fixes that too.

Added a regression test for the bare mention case, run against both builds. Full suite is 2203 passed, 0 failed, including the test that came with this PR. A 6 MB payload with a preceding bare mention strips in about 17 ms.

Nothing else in the diff looked wrong to me. The copyCursor/searchCursor bookkeeping, the character classes, the case insensitive compare, and the replaced fast path all check out.

馃 Review by Claude Code

@esokullu
esokullu merged commit da5cd71 into webbrain-one:main Sep 7, 2026
0 of 2 checks passed
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