Repository navigation
fix: render markdown in the Thinking block and never load images from model text - #11
Merged
Merged
Conversation
|
AI review | Looks safe to merge Introduces a shared SafeMarkdown component that renders model output with GFM support while explicitly blocking raw HTML and image loading to prevent XSS and data leakage. Adds table styling and comprehensive frontend tests without introducing new dependencies.
Reviewed 82ae156. AI can be wrong, a maintainer still decides. Comment |
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.
Summary
The Thinking block printed the model's reasoning summary as plain text, so markdown showed up raw, e.g.
**Evaluating AI assistant plugins**. It is now rendered as markdown. No new dependency.Thinking and assistant replies now share one
SafeMarkdowncomponent: raw HTML is never rendered, and markdown images are never loaded, so model text that echoes host or tool output cannot make the browser fetch a remote URL (and leak data in it) just by being shown.Base:
dev-1.3.0. Independent of #9 and #10 (neither touches these files), so it can merge in any order.Changes
src/frontend/markdown.tsx(new):SafeMarkdownrendersreact-markdownwithremark-gfmanddisallowedElements={["img"]}, withoutrehype-raw.src/frontend/AiThinking.tsx: renders the reasoning withSafeMarkdown, with compact styling for the collapsed panel (tighter paragraph/list margins, code andprestyling likeAiMessage). The toggle, the collapsed default, mutedtext-xs,max-h-64scroll and left border are unchanged;whitespace-pre-wrapis dropped because markdown now handles line breaks.src/frontend/AiMessage.tsx: assistant replies useSafeMarkdown(same wrapper and styling as before). Behaviour change: a markdown image in a reply is no longer shown. User messages are still plain text.style:commit): GFM tables were already unstyled ondev-1.3.0(notable/th/tdrules at all, so cells sat glued together, e.g. "AB / 12 / 34"); the rendered HTML before and after this PR's earlier commits is identical.SafeMarkdownnow mapstable/th/td:my-2 max-w-full overflow-x-autowrapper, so wide tables scroll horizontally instead of stretching the panel; the table isw-max border-collapse;th:border border-border bg-muted px-2 py-1 text-left font-semibold;td:border border-border px-2 py-1 align-top;text-alignstyle), and react-markdown'snodeprop is not passed to the DOM.dist/frontend.css(w-max,border-collapse,max-w-fullandalign-topare new rules).tests/frontend/AiThinking.test.tsx(4): bold renders as<strong>with no literal**;<img onerror>/<script>content creates no elements and runs nothing; markdown images are not loaded; collapsed by default.tests/frontend/AiMessage.test.tsx(5): bold in a reply renders as<strong>; raw HTML creates no elements; markdown images are not loaded; a 2x2 table renders a<table>with 2thand 4tdinside anoverflow-x-autowrapper, every cell withpx-2 border border-borderand nonodeattribute; user messages stay plain text.Verification
Finish condition:
**text**in a reasoning summary renders bold, raw HTML and images in model text never become elements, tables have padded, bordered cells and scroll when wide, and the repo's checks pass.Commands (Node 24 per
.nvmrc, same as CI):tsc -p .✅ ·prettier --check .✅vitest run✅: 88 files, 798 passed, 1 skipped (basedev-1.3.0: 86 files, 789 passed, 1 skipped)termix-plugin test / typecheck / build / validate / pack✅The tests catch deliberately broken fixes (each applied alone, then reverted):
{content}as plain text againdangerouslySetInnerHTML(bold via regex)SafeMarkdown(disallowedElements={[]})AiMessageback to plainReactMarkdownAiMessageviadangerouslySetInnerHTMLcomponentsmapping (tables unstyled again)overflow-x-autofrom the table wrapperpx-2 py-1fromtdManual test
Throwaway Termix container (
ghcr.io/termix-ssh/termix:latest) with these commits on top of #10, mounted as the bundledaiplugin:Loaded plugin ai@1.3.0,Activated plugin ai. Modelgpt-6-luna.Prompt: "Reply with exactly this markdown and nothing else: **bold text**, a bulleted list with two items, a small 2x2 table,  and <b>html</b>". Result (two replies):
style:commit it showed as "AB / 12 / 34");<b>html</b>stayed literal text;**(an earlier run showed "Inspecting plugin versions" the same way).Screenshots
Earlier run: Thinking heading rendered bold