What Bones

Markdown

One sanitized Markdown renderer for blog posts, the terms, and chatbot replies.

Why Markdown

Blog posts, the terms and conditions, and chatbot replies are all Markdown. Posts and terms are stored as plain text. The editor in the admin is a textarea with a preview tab, the same shape as GitHub's, not a rich-text editor.

The pipeline

MarkdownViewer in shared-ui renders all of it, in the web app, the desktop app, the blog, and the chatbot:

  • react-markdown turns Markdown into React elements, not an HTML string, so nothing goes through innerHTML.
  • remark-gfm adds GitHub's extensions: tables, task lists, strikethrough, and autolinks.
  • rehype-raw parses HTML written inside the Markdown. The terms need it for their <span> markup.
  • rehype-sanitize then strips scripts, event handlers, and javascript: links. It uses GitHub's own allowlist, with one addition: class on <span>.

Parsing raw HTML is only safe with the sanitizer after it. The terms are public and written by an admin, and the sanitizer keeps a compromised admin account from planting a script for every visitor. Changing the sanitizer's schema needs a security review.

The cost

The pipeline is large. Adding it pushed the web app's JavaScript past Vite's 500 kB warning. Loading it only on the pages that render Markdown isn't built yet.

Used in