fix(site/src/pages/AgentsPage): paste SVG source inline instead of as attachment#26969
Draft
jakehwll wants to merge 2 commits into
Draft
fix(site/src/pages/AgentsPage): paste SVG source inline instead of as attachment#26969jakehwll wants to merge 2 commits into
jakehwll wants to merge 2 commits into
Conversation
…as attachment Pasting SVG XML text into the /agents chat input surfaced "Unsupported file type." because the large-paste heuristic wrapped it into a text/plain File. The server-side classifier in coderd/x/chatfiles/mime.go probes for an SVG root element before trusting the declared MIME type and rejects the resulting attachment. Detect the SVG root element in the paste helper so oversized SVG sources bypass the attachment path and land inline in the Lexical editor. Uploads via file picker or drag-and-drop are unaffected.
…tection Replace the hand-rolled XML tokenizer in hasSVGRootElement with a DOMParser call. The browser's XML parser handles BOM, XML declarations, comments, and DOCTYPE prologs natively, so the helper collapses to a parse-and-inspect check that reads far more clearly. The check remains lenient by design: a false positive leaves an XML-ish paste inline, which is harmless, whereas a false negative reproduces the "Unsupported file type." bug. The removed test covered a synthetic combined prolog (BOM + XML decl + comment + DOCTYPE) that jsdom rejects but that no real-world SVG produces.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pasting SVG XML text into the
/agentschat input surfacedUnsupported file type.because the large-paste heuristic wrapped it into atext/plainFile and the server-side classifier incoderd/x/chatfiles/mime.goprobes for an SVG root element before trusting the declared MIME type. SVG uploads are intentionally rejected because SVG can carry active script.Detect the SVG root element on the paste path so oversized SVG sources bypass the attachment path and land inline in the Lexical editor. SVG uploads via file picker or drag-and-drop are unchanged; both paths already filter
image/svg+xmlbefore it reaches the upload pipeline.Implementation plan
Root cause
PasteSanitizationPlugininsite/src/pages/AgentsPage/components/ChatMessageInput/ChatMessageInput.tsxextracts the plain text.isLargePaste(text)(>=10 lines OR >=1000 chars) wraps the paste into a synthetic.txtFile with MIMEtext/plainviacreatePasteFile().onFilePaste->handleFilePaste->onAttach->useFileAttachments.handleAttach->startUpload->API.experimental.uploadChatFile().postChatFilecallschatfiles.PrepareStoredFile()->ClassifyStoredMediaType(), which invokesHasSVGRootElement(data)first. That returnsimage/svg+xmlregardless of the declaredContent-Type.image/svg+xmlis not incodersdk.AllChatAttachmentMediaTypes, so the server returns HTTP 400 withMessage: "Unsupported file type.".Why the fix inspects content
SVG source copied from a text editor arrives on the clipboard as
text/plain; there is noimage/svg+xmlentry on the clipboard to consult, and the synthetic File we construct istext/plainby design. The server sniffs bytes and reclassifies toimage/svg+xml, so the only thing that predicts the server's decision is the content itself.Fix
Ask the browser's XML parser to classify the paste. If it parses as an SVG document with an
<svg>root element, skip the large-paste-to-attachment conversion and insert the text inline instead. Kept lenient by design: a false positive just leaves an XML-ish paste inline (harmless), whereas a false negative reproduces the original bug.Changes
site/src/pages/AgentsPage/components/ChatMessageInput/pasteHelpers.ts: addhasSVGRootElement(text)backed byDOMParser.parseFromString(text, "image/svg+xml"). Returnstruewhen parsing succeeds without aparsererrorand the root element is<svg>.site/src/pages/AgentsPage/components/ChatMessageInput/ChatMessageInput.tsx: gate both paste branches (nativeClipboardEventandbeforeinput) on!hasSVGRootElement(text)before invokingcreatePasteFile(text)and callingonFilePaste.site/src/pages/AgentsPage/components/ChatMessageInput/pasteHelpers.test.ts: unit tests covering bare<svg>, self-closing<svg/>, attributed<svg xmlns=...>, XML declaration prolog, DOCTYPE, comments, BOM, case-insensitivity, lookalike names, HTML, markdown, CSV fragments, whitespace-only, unterminated PIs and comments.site/src/pages/AgentsPage/components/AgentChatInput.stories.tsx:LargeSVGPasteRemainsInlinestory that pastes an SVG larger than theisLargePastethreshold and asserts it lands inline without callingonAttach, alongside the existingLargePasteCreatesAttachmentPreviewandCtrlShiftVBypassesAttachmentCollapsestories.Scope
chatAttachmentAcceptAttributedoes not include SVG.AgentChatInput.handleDropfilters viaisChatAttachmentFile, which excludesimage/svg+xml.filesarray is filtered byisChatAttachmentFilein the plugin's native-paste branch.Validation
pnpm --dir site exec vitest run --project=unit src/pages/AgentsPage/components/ChatMessageInput/pasteHelpers.test.ts-> 33/33 pass.pnpm --dir site exec vitest run --project=unit src/pages/AgentsPage/components/ChatMessageInput/ChatMessageInput.test.tsx-> 3/3 pass.pnpm --dir site exec vitest run --project=storybook src/pages/AgentsPage/components/AgentChatInput.stories.tsx-> 48/48 pass in headless Chromium.pnpm --dir site run lint:types-> 0 errors.pnpm --dir site exec biome checkon the touched files -> clean.