JSFiddle - React, Tailwind, and code Playground

by Jason Miller

HTML

<script src="https://unpkg.com/snarkdown@1.2.2/dist/snarkdown.umd.js"></script>
<label>source: <em>(compiles as you type)</em>
<textarea id="input">
# XSS Mitigation

Add scripts, frames, malformed attributes, etc.

&lt;script&gt;console.log('inline script')&lt;/script&gt;

&lt;img onload="console.log('img onload')"&gt;
</textarea>
</label>

<label>HTML:
<textarea readonly id="output"></textarea>
</label>

<label>Rendered:
<div readonly id="rendered"></div>
</label>

CSS

html, body {
  background: #eee;
  font-family: system-ui;
}

label {
  display: block;
  margin: 10px auto;
  width: 90%;
  font-weight: bold;
}

textarea {
  display: block;
  width: 100%;
  height: 27vh;
  white-space: pre-wrap;
  font: 14px 'Source Code Pro', Monaco, 'Courier New', monospace;
}
#rendered {
  font-weight: normal;
  display: block;
  width: 100%;
  height: 27vh;
  background: #FFF;
  border: 1px solid #ccc;
}

em { font-weight: normal; color: #555; }
#error {
  color: red;
}
#error:empty {
  display: none;
}

JavaScript

(input.oninput = () => {
  const html = safeMarkdown(input.value);
  output.value = html;
  rendered.innerHTML = html;
})();

function safeMarkdown(markdown) {
  const html = snarkdown(markdown);
  const doc = new DOMParser().parseFromString(
    `<!DOCTYPE html>\n<html><body>${html}`,
    'text/html'
  );
  doc.normalize();
  sanitize(doc.body);
  return doc.body.innerHTML;
}
function sanitize(node) {
	if (node.nodeType === 3) return;
  if (node.nodeType !== 1 || /^(script|iframe|object|embed|svg)$/i.test(node.tagName)) {
    return node.remove();
  }
  for (let i=node.attributes.length; i--; ) {
    const name = node.attributes[i].name;
    if (!/^(class|id|name|href|src|alt|align|valign)$/i.test(name)) {
      node.attributes.removeNamedItem(name);
    }
  }
  for (let i=node.childNodes.length; i--; ) {
  	sanitize(node.childNodes[i]);
  }
}