agentsclimarketplace

Markdown sanitization chain

Skill fabioc-aloha/Alex_ACT_Edition/.github/skills/markdown-sanitization-chain

Render user-supplied markdown safely — marked.js → DOMPurify → Mermaid (order matters; skipping the sanitizer is XSS)From its SKILL.md

Install
npx -y skills add fabioc-aloha/Alex_ACT_Edition --skill markdown-sanitization-chain

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

3.4 KB, 783 tokens by cl100k_base, as published. Nobody here has run it

Markdown Sanitization Chain

Battle-tested via production XSS incident. The order of markdown → sanitize → diagram render is non-negotiable when content comes from users.

When to Use

  • An app renders markdown supplied by users (comments, docs UI, embedded editors)
  • You're about to call innerHTML with markdown-derived HTML
  • Mermaid or another diagram renderer runs in the browser
  • Security review on a markdown-rendering surface

Why It Matters

Markdown renderers (marked.js, markdown-it) convert markdown to HTML but do not sanitize it. Diagram renderers (Mermaid, PlantUML) execute after sanitizers run, which can re-introduce attack vectors. Order matters critically.

The Rule

Always: marked.js → DOMPurify → Mermaid (post-render).

1. Parse markdown to HTML        (marked.js)
2. Sanitize HTML                 (DOMPurify)
3. Insert sanitized HTML into DOM
4. Render diagrams on the now-sanitized DOM (Mermaid.run())

Never skip the sanitizer even if content is "trusted." Trust gets revoked when the threat model changes; the chain stays.

Implementation

import { marked } from 'marked';
import DOMPurify from 'dompurify';
import mermaid from 'mermaid';

async function renderMarkdown(content, container) {
  // Step 1: parse markdown to HTML
  const rawHtml = marked.parse(content);

  // Step 2: sanitize BEFORE inserting into the DOM
  const cleanHtml = DOMPurify.sanitize(rawHtml, {
    ADD_TAGS: ['mermaid'], // allow mermaid tags through
  });

  // Step 3: insert sanitized HTML
  container.innerHTML = cleanHtml;

  // Step 4: render diagrams on sanitized DOM
  await mermaid.run({ nodes: container.querySelectorAll('.mermaid') });
}

Common Mistakes

MistakeConsequence
Skip DOMPurify ("it's internal content")XSS from any content source
Sanitize after Mermaid rendersMermaid-injected scripts execute
Use innerHTML without sanitization anywhereClassic XSS
Trust localStorage / URL paramsUser-controlled XSS payloads

DOMPurify Configuration

const config = {
  ADD_TAGS: ['mermaid'],            // preserve diagram tags
  ADD_ATTR: ['onclick'],            // only if absolutely needed
  FORBID_TAGS: ['style', 'script'], // explicit blocklist
  FORBID_ATTR: ['onerror', 'onload'],
};

Verification Checklist

  • Markdown parser runs first
  • DOMPurify runs before DOM insertion
  • Diagram renderer runs after sanitization
  • No raw innerHTML without sanitization anywhere in the surface
  • Tested with <img src=x onerror=alert(1)> payload

Related

Would Revise If

Revisit this skill by 2026-08-26 (90 days) or sooner if any of the following fires: DOMPurify or marked.js publishes a breaking change that invalidates the documented chain order; a real XSS payload bypasses the chain in production use; or Mermaid changes its render-time HTML interface in a way that makes the post-sanitization step unsafe.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most docs writing skills give in 783 tokens

Counted across 1,637 of the 3,044 authors here whose files we hold, read 2026-08-07

  • Announce the skill at startin 54 of 1637, across 26 files
  • Convert legacy doc files before editingin 45 of 1637, across 7 files
  • Predict questions readers might askin 42 of 1637, across 4 files
  • Generate clarifying questions for initial contextin 42 of 1637, across 3 files
  • Create document scaffold with placeholder textin 42 of 1637, across 3 files
  • Brainstorm content options for each sectionin 42 of 1637, across 3 files
  • Test the document with a fresh context-less instancein 42 of 1637, across 3 files
  • Include exact file paths in every taskin 42 of 1637, across 15 files
  • Ask interview questions one at a timein 42 of 1637, across 27 files
  • Apply surgical edits during refinementin 41 of 1637, across 2 files
  • Offer structured workflow or freeformin 40 of 1637, across 1 file
  • Ask for document meta-contextin 40 of 1637, across 2 files

Said here and by no other author read

  • parse markdown to html first
  • sanitize html before inserting into dom
  • run diagram renderers after sanitization
  • sanitize html even if content is trusted
  • explicitly forbid script and style tags
  • explicitly forbid onerror and onload attributes

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 326,512. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.