filter with dompurify

This commit is contained in:
Zimeng Xiong
2025-11-22 21:21:28 -08:00
parent 69bffab745
commit cd9c242983
3 changed files with 788 additions and 28 deletions
+590
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -14,13 +14,16 @@
"dependencies": {
"@prisma/client": "^5.22.0",
"@types/archiver": "^7.0.0",
"@types/jsdom": "^27.0.0",
"@types/multer": "^2.0.0",
"@types/socket.io": "^3.0.1",
"archiver": "^7.0.1",
"better-sqlite3": "^12.4.6",
"cors": "^2.8.5",
"dompurify": "^3.3.0",
"dotenv": "^17.2.3",
"express": "^5.1.0",
"jsdom": "^27.2.0",
"multer": "^2.0.2",
"socket.io": "^4.8.1",
"zod": "^4.1.12"
+195 -28
View File
@@ -3,50 +3,166 @@
*/
import { z } from "zod";
import DOMPurify from "dompurify";
import { JSDOM } from "jsdom";
// Create a DOM environment for DOMPurify (Node.js compatibility)
const window = new JSDOM("").window;
const purify = DOMPurify(window);
/**
* Sanitize HTML/JS content by removing dangerous patterns
* Sanitize HTML/JS content using DOMPurify (battle-tested library)
*/
export const sanitizeHtml = (input: string): string => {
if (typeof input !== "string") return "";
return input
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "") // Remove script tags
.replace(/javascript:/gi, "") // Remove javascript: URIs
.replace(/on\w+\s*=\s*["'][^"']*["']/gi, "") // Remove event handlers
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "") // Remove iframes
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, "") // Remove objects
.replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, "") // Remove embeds
.replace(/<link\b[^>]*>/gi, "") // Remove link tags
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "") // Remove style tags
return purify
.sanitize(input, {
ALLOWED_TAGS: [
// Allow basic text formatting that might be in drawings
"b",
"i",
"u",
"em",
"strong",
"p",
"br",
"span",
"div",
],
ALLOWED_ATTR: [], // No attributes allowed by default for security
FORBID_TAGS: [
// Explicitly forbid dangerous tags
"script",
"iframe",
"object",
"embed",
"link",
"style",
"form",
"input",
"button",
"select",
"textarea",
"svg",
"foreignObject",
],
FORBID_ATTR: [
// Explicitly forbid dangerous attributes
"onload",
"onclick",
"onerror",
"onmouseover",
"onfocus",
"onblur",
"onchange",
"onsubmit",
"onreset",
"onkeydown",
"onkeyup",
"onkeypress",
"href",
"src",
"action",
"formaction",
],
KEEP_CONTENT: true, // Keep content even if tags are removed
})
.trim();
};
/**
* Sanitize SVG content specifically
* Sanitize SVG content using DOMPurify with strict SVG restrictions
*/
export const sanitizeSvg = (svgContent: string): string => {
if (typeof svgContent !== "string") return "";
// Remove potentially dangerous SVG elements and attributes
return svgContent
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/javascript:/gi, "")
.replace(/on\w+\s*=\s*["'][^"']*["']/gi, "")
.replace(
/<foreignObject\b[^<]*(?:(?!<\/foreignObject>)<[^<]*)*<\/foreignObject>/gi,
""
)
.replace(/\shref\s*=\s*["'][^"']*(?:javascript:)[^"']*["']/gi, ' href="#"')
.replace(
/\sxlink:href\s*=\s*["'][^"']*(?:javascript:)[^"']*["']/gi,
' xlink:href="#"'
)
// For SVG content, we'll be very restrictive since SVG can execute JavaScript
// We only allow basic geometric shapes without any scripts or external references
return purify
.sanitize(svgContent, {
ALLOWED_TAGS: [
// Allow only safe SVG geometric elements
"svg",
"g",
"rect",
"circle",
"ellipse",
"line",
"polyline",
"polygon",
"path",
"text",
"tspan",
],
ALLOWED_ATTR: [
// Allow only safe geometric attributes
"x",
"y",
"width",
"height",
"cx",
"cy",
"r",
"rx",
"ry",
"x1",
"y1",
"x2",
"y2",
"points",
"d",
"fill",
"stroke",
"stroke-width",
"opacity",
"transform",
"font-size",
"font-family",
"text-anchor",
"dominant-baseline",
],
FORBID_TAGS: [
// Completely forbid any script-related or external content
"script",
"foreignObject",
"iframe",
"object",
"embed",
"use",
"image",
"style",
"link",
"defs",
"symbol",
"marker",
"clipPath",
"mask",
"filter",
],
FORBID_ATTR: [
// Forbid any attributes that could execute code or load external content
"onload",
"onclick",
"onerror",
"onmouseover",
"onfocus",
"onblur",
"href",
"xlink:href",
"src",
"action",
"style",
"class",
"id",
],
KEEP_CONTENT: true,
})
.trim();
};
/**
* Validate and sanitize text content
* Validate and sanitize text content using DOMPurify
*/
export const sanitizeText = (
input: unknown,
@@ -60,8 +176,59 @@ export const sanitizeText = (
// Truncate if too long
const truncated = cleaned.slice(0, maxLength);
// Final HTML sanitization
return sanitizeHtml(truncated);
// Use DOMPurify for text content - more permissive than HTML but still safe
return purify
.sanitize(truncated, {
ALLOWED_TAGS: [
// Allow basic text formatting that might be in drawing text
"b",
"i",
"u",
"em",
"strong",
"br",
"span",
],
ALLOWED_ATTR: [], // No attributes allowed for text content
FORBID_TAGS: [
// Block potentially dangerous tags
"script",
"iframe",
"object",
"embed",
"link",
"style",
"form",
"input",
"button",
"select",
"textarea",
"svg",
"foreignObject",
],
FORBID_ATTR: [
// Block all event handlers and dangerous attributes
"onload",
"onclick",
"onerror",
"onmouseover",
"onfocus",
"onblur",
"onchange",
"onsubmit",
"onreset",
"onkeydown",
"onkeyup",
"onkeypress",
"href",
"src",
"action",
"formaction",
"style",
],
KEEP_CONTENT: true,
})
.trim();
};
/**