Skip to content

test: cover markdown.tsx, the sole renderer of untrusted LLM output - #134

Open
shrdgn wants to merge 1 commit into
mainfrom
claude/markdown-component-tests
Open

test: cover markdown.tsx, the sole renderer of untrusted LLM output#134
shrdgn wants to merge 1 commit into
mainfrom
claude/markdown-component-tests

Conversation

@shrdgn

@shrdgn shrdgn commented Aug 3, 2026

Copy link
Copy Markdown
Owner

What & why

web/src/components/markdown.tsx renders every panel/synthesized answer shown in the playground (App.tsx's panel cards and fused-answer view) and is the only place untrusted model output reaches the DOM. It had zero tests anywhere under web/src/__tests__ or colocated, despite every other component/lib module having coverage.

react-markdown v9 is safe by default here (no rehype-raw plugin is wired up, so raw HTML in markdown source renders as inert escaped text rather than live DOM nodes) — but that safety property was entirely unenforced by any test. A future change (e.g. adding rehypeRaw to support HTML tables, or a custom img/a renderer) could silently reintroduce script execution from model output with nothing catching it in CI.

What changed

Added web/src/components/__tests__/markdown.test.tsx covering:

  • Plain text rendering.
  • GFM formatting (bold, list, table) renders as real DOM elements (<strong>, <li>, <table>).
  • A raw <script> tag in markdown source renders as inert escaped text (container.querySelector("script") is null, but the escaped text is visible) — never a live <script> element, and window.__pwned is never set.
  • A raw <img onerror> handler renders the same way — never a live <img> element with an onerror attribute.
  • A markdown link renders as a real <a href>.

The <script>/<img onerror> tests are regression tests that lock in the exact current behavior (verified against the real rendered DOM, not assumed) so it fails loudly if a future PR changes the render pipeline in a way that reintroduces raw-HTML execution.

How it was tested

  • npm run typecheck passes
  • npm run test:coverage passes: 55 tests (was 50), overall coverage 91.59%/81.26%/88.31%/93.73% (statements/branches/functions/lines), above the 90/70/85/90 CI gate

Generated by Claude Code

markdown.tsx renders every panel/synthesized answer shown in the playground
and is the only place model output reaches the DOM, but had zero tests.
Adds coverage for plain text, GFM formatting (bold/list/table), and two
regression tests locking in the component's XSS-safety property: a raw
<script> or <img onerror> in markdown source renders as inert escaped text,
never a live DOM element, since react-markdown has no rehype-raw plugin
wired up. A future change adding one (e.g. to support HTML tables) will now
trip a test instead of silently reintroducing script execution.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdajDh7gW5bAjvGmmMpdt2
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