#21: add explicit DOMPurify HTML sanitizer test suite and refine remote image blocker

This commit is contained in:
Lucas Rayan committed 2026-08-02 10:53:44 -03:00
1 parent 1707ade6da
commit 86aad5087b
2 files changed
+51 -7

No files matched your search

+15 -7
View File
@@ -5,12 +5,18 @@ import DOMPurify from "dompurify";
* Strips scripts, dangerous attributes, and executable elements while keeping layout. * Strips scripts, dangerous attributes, and executable elements while keeping layout.
*/ */
export function sanitizeEmailHtml(rawHtml: string): string { export function sanitizeEmailHtml(rawHtml: string): string {
if (typeof window === "undefined") { if (!rawHtml) return "";
// SSR fallback: basic strip of script tags
return rawHtml.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
}
return DOMPurify.sanitize(rawHtml, { // Base sanitization stripping dangerous tags and inline event handlers
let cleaned = rawHtml
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "")
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, "")
.replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, "")
.replace(/\s*on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
if (typeof window !== "undefined") {
cleaned = DOMPurify.sanitize(cleaned, {
ALLOWED_TAGS: [ ALLOWED_TAGS: [
"a", "b", "blockquote", "br", "caption", "code", "div", "em", "h1", "h2", "h3", "a", "b", "blockquote", "br", "caption", "code", "div", "em", "h1", "h2", "h3",
"h4", "h5", "h6", "hr", "i", "img", "li", "ol", "p", "pre", "span", "strong", "h4", "h5", "h6", "hr", "i", "img", "li", "ol", "p", "pre", "span", "strong",
@@ -23,10 +29,12 @@ export function sanitizeEmailHtml(rawHtml: string): string {
"src", "style", "title", "width", "target", "rel", "data-blocked-src" "src", "style", "title", "width", "target", "rel", "data-blocked-src"
], ],
ALLOW_DATA_ATTR: true, ALLOW_DATA_ATTR: true,
ADD_ATTR: ["target"],
FORBID_TAGS: ["script", "iframe", "object", "embed", "form", "input", "button"], FORBID_TAGS: ["script", "iframe", "object", "embed", "form", "input", "button"],
FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover", "onfocus"], FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover", "onfocus"],
}); });
}
return cleaned;
} }
/** /**
@@ -36,7 +44,7 @@ export function sanitizeEmailHtml(rawHtml: string): string {
export function blockRemoteImages(html: string): string { export function blockRemoteImages(html: string): string {
if (!html) return ""; if (!html) return "";
return html return html
.replace(/(<img[^>]+)src\s*=\s*("|')https?:[^"']*\2/gi, "$1data-blocked-src=$2$2") .replace(/(<img\b[^>]*?\b)src\s*=\s*("|')(https?:[^"']*)\2/gi, "$1data-blocked-src=$2$3$2")
.replace(/(<[^>]+)background\s*=\s*("|')https?:[^"']*\2/gi, "$1") .replace(/(<[^>]+)background\s*=\s*("|')https?:[^"']*\2/gi, "$1")
.replace(/url\(\s*['"]?https?:[^)]*\)/gi, "none"); .replace(/url\(\s*['"]?https?:[^)]*\)/gi, "none");
} }
+36
View File
@@ -0,0 +1,36 @@
import { describe, it, expect } from "vitest";
import { sanitizeEmailHtml, blockRemoteImages, unblockRemoteImages } from "./lib/sanitizer.js";
describe("DOMPurify HTML Sanitizer (Section 14.2)", () => {
it("strips malicious script tags and inline event handlers", () => {
const maliciousHtml = `<div><script>alert('xss')</script><img src="x" onerror="alert('hack')" /><p>Safe text</p></div>`;
const sanitized = sanitizeEmailHtml(maliciousHtml);
expect(sanitized).not.toContain("<script>");
expect(sanitized).not.toContain("onerror");
expect(sanitized).toContain("<p>Safe text</p>");
});
it("strips iframe and object tags", () => {
const htmlWithIframe = `<div><iframe src="http://malicious.com"></iframe><object data="test.swf"></object><span>Hello</span></div>`;
const sanitized = sanitizeEmailHtml(htmlWithIframe);
expect(sanitized).not.toContain("<iframe");
expect(sanitized).not.toContain("<object");
expect(sanitized).toContain("<span>Hello</span>");
});
it("blocks remote tracking images by default", () => {
const html = `<p>Test</p><img src="https://tracker.com/pixel.png" alt="tracker" />`;
const sanitized = sanitizeEmailHtml(html);
const blocked = blockRemoteImages(sanitized);
expect(blocked).toContain('data-blocked-src="https://tracker.com/pixel.png"');
expect(blocked).not.toContain(' src="https://tracker.com/pixel.png"');
});
it("unblocks remote images when requested by user", () => {
const html = `<p>Test</p><img src="https://tracker.com/pixel.png" alt="tracker" />`;
const sanitized = sanitizeEmailHtml(html);
const blocked = blockRemoteImages(sanitized);
const unblocked = unblockRemoteImages(blocked);
expect(unblocked).toContain('src="https://tracker.com/pixel.png"');
});
});