From cbe44ffd36fd9eeee4cedcbd0d9f392b4e0792ee Mon Sep 17 00:00:00 2001 From: pkupuk Date: Wed, 11 Mar 2026 21:47:16 +0800 Subject: [PATCH] fix(frontend): add fallback for DOMPurify in Cloudflare Workers --- apps/frontend/src/lib/sanitize.ts | 80 ++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/apps/frontend/src/lib/sanitize.ts b/apps/frontend/src/lib/sanitize.ts index cb95c2b..f1fb6bc 100644 --- a/apps/frontend/src/lib/sanitize.ts +++ b/apps/frontend/src/lib/sanitize.ts @@ -4,37 +4,61 @@ */ import DOMPurify from 'isomorphic-dompurify' +/** + * Basic SVG sanitization for environments without DOM + * Removes dangerous tags and attributes + */ +const basicSanitize = (svg: string): string => { + // Remove script tags and their content + let sanitized = svg.replace(/)<[^<]*)*<\/script>/gi, '') + // Remove dangerous attributes + sanitized = sanitized.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, '') + // Remove other dangerous tags + sanitized = sanitized.replace(/<(iframe|object|embed|style)[^>]*>.*?<\/\1>/gi, '') + return sanitized +} + /** * Sanitize SVG content to prevent XSS attacks * Only allows safe SVG elements and attributes */ export const sanitizeSvg = (svg: string): string => { - return DOMPurify.sanitize(svg, { - USE_PROFILES: { svg: true, svgFilters: true }, - ADD_TAGS: ['use', 'defs', 'symbol'], - ADD_ATTR: [ - 'viewBox', - 'fill', - 'class', - 'stroke', - 'stroke-width', - 'd', - 'cx', - 'cy', - 'r', - 'x', - 'y', - 'width', - 'height', - 'transform', - 'xmlns', - 'xmlns:xlink', - 'xlink:href', - 'preserveAspectRatio', - 'clip-rule', - 'fill-rule', - ], - FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'style'], - FORBID_ATTR: ['onload', 'onerror', 'onclick', 'onmouseover'], - }) + // Check if DOMPurify is available (has sanitize method) + if (typeof DOMPurify?.sanitize === 'function') { + try { + return DOMPurify.sanitize(svg, { + USE_PROFILES: { svg: true, svgFilters: true }, + ADD_TAGS: ['use', 'defs', 'symbol'], + ADD_ATTR: [ + 'viewBox', + 'fill', + 'class', + 'stroke', + 'stroke-width', + 'd', + 'cx', + 'cy', + 'r', + 'x', + 'y', + 'width', + 'height', + 'transform', + 'xmlns', + 'xmlns:xlink', + 'xlink:href', + 'preserveAspectRatio', + 'clip-rule', + 'fill-rule', + ], + FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'style'], + FORBID_ATTR: ['onload', 'onerror', 'onclick', 'onmouseover'], + }) + } catch { + // Fallback to basic sanitization if DOMPurify fails + return basicSanitize(svg) + } + } + // Fallback for non-browser environments (Cloudflare Workers) + return basicSanitize(svg) }