const { expect } = require("chai"); const { marked } = require("marked"); const sanitizeHtml = require("sanitize-html"); const sanitizeOptions = { allowedTags: sanitizeHtml.defaults.allowedTags.concat([ "img", "video", "input", "details", "summary", "del", "ins", "sup", "sub", ]), allowedAttributes: { ...sanitizeHtml.defaults.allowedAttributes, img: ["src", "srcset", "alt", "title", "width", "height", "loading"], video: ["src", "controls", "title"], input: ["type", "checked", "disabled"], code: ["class"], span: ["class"], div: ["class"], pre: ["class"], td: ["align"], th: ["align"], }, allowedSchemes: ["http", "https", "mailto"], }; /** * Mirrors the server-side rendering pipeline in webview.ts: * sanitizeHtml(marked(content, opts), sanitizeOptions) */ function renderAndSanitize(markdown) { const raw = marked(markdown, { headerIds: false, mangle: false }); return sanitizeHtml(raw, sanitizeOptions); } describe("Markdown sanitization", function () { // --------------------------------------------------------------- // Script injection // --------------------------------------------------------------- describe("removes script tags", function () { it("strips inline '); expect(html).to.not.include("' ); expect(html).to.not.include("document.cookie\n\nWorld" ); expect(html).to.not.include("'); expect(html).to.not.include("onerror"); }); it("strips onload handler on img", function () { const html = renderAndSanitize( '' ); expect(html).to.not.include("onload"); }); it("strips onmouseover handler on a tag", function () { const html = renderAndSanitize( 'hover me' ); expect(html).to.not.include("onmouseover"); expect(html).to.include("hover me"); }); it("strips onfocus handler on input", function () { const html = renderAndSanitize(''); expect(html).to.not.include("onfocus"); }); }); // --------------------------------------------------------------- // javascript: URLs // --------------------------------------------------------------- describe("removes javascript: URLs", function () { it("strips javascript: href in anchor", function () { const html = renderAndSanitize( 'click' ); expect(html).to.not.include("javascript:"); }); it("strips javascript: href in markdown link syntax", function () { const html = renderAndSanitize("[click](javascript:alert(1))"); expect(html).to.not.include("javascript:"); }); }); // --------------------------------------------------------------- // iframe / object / embed // --------------------------------------------------------------- describe("removes dangerous elements", function () { it("strips iframe", function () { const html = renderAndSanitize( '' ); expect(html).to.not.include("' ); expect(html).to.not.include("'); expect(html).to.not.include("' ); expect(html).to.not.include("'); expect(html).to.not.include("onload"); }); it("strips svg with embedded script", function () { const html = renderAndSanitize( "" ); expect(html).to.not.include("click' ); expect(html).to.not.include("data:text/html"); }); }); // --------------------------------------------------------------- // style-based attacks // --------------------------------------------------------------- describe("removes style-based attacks", function () { it("strips style tags with expressions", function () { const html = renderAndSanitize( "" ); expect(html).to.not.include("javascript:"); }); }); // --------------------------------------------------------------- // Safe content is preserved // --------------------------------------------------------------- describe("preserves safe markdown content", function () { it("preserves headings", function () { const html = renderAndSanitize("# Heading 1\n## Heading 2"); expect(html).to.include("

"); expect(html).to.include("Heading 1"); expect(html).to.include("

"); }); it("preserves paragraphs", function () { const html = renderAndSanitize("Hello world\n\nSecond paragraph"); expect(html).to.include("

"); expect(html).to.include("Hello world"); }); it("preserves bold and italic", function () { const html = renderAndSanitize("**bold** and *italic*"); expect(html).to.include("bold"); expect(html).to.include("italic"); }); it("preserves links", function () { const html = renderAndSanitize("[example](https://example.com)"); expect(html).to.include("https://example.com"); expect(html).to.include("example"); }); it("preserves images", function () { const html = renderAndSanitize( "![alt](https://example.com/img.png)" ); expect(html).to.include("npm install"); }); it("preserves unordered lists", function () { const html = renderAndSanitize("- item 1\n- item 2\n- item 3"); expect(html).to.include("

    "); expect(html).to.include("
  • "); expect(html).to.include("item 1"); }); it("preserves ordered lists", function () { const html = renderAndSanitize("1. first\n2. second"); expect(html).to.include("
      "); expect(html).to.include("first"); }); it("preserves blockquotes", function () { const html = renderAndSanitize("> This is a quote"); expect(html).to.include("
      "); expect(html).to.include("This is a quote"); }); it("preserves tables", function () { const html = renderAndSanitize("| A | B |\n|---|---|\n| 1 | 2 |"); expect(html).to.include(""); expect(html).to.include("
      "); expect(html).to.include(""); }); it("preserves horizontal rules", function () { const html = renderAndSanitize("---"); expect(html).to.include("alert("xss")\n\n**Bold text**' ); expect(html).to.not.include("Bold text"); }); it("strips event handlers from otherwise-safe tags", function () { const html = renderAndSanitize( 'photo' ); expect(html).to.not.include("onerror"); expect(html).to.include("photo.jpg"); expect(html).to.include('alt="photo"'); }); }); });