diff --git a/apps/frontend/package.json b/apps/frontend/package.json index da11a6f..3caa432 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -18,8 +18,7 @@ "agentation": "^2.1.1", "agentation-mcp": "^1.1.0", "astro": "6.0.0-beta.17", - "better-auth": "^1.3.13", - "isomorphic-dompurify": "^3.0.0" + "better-auth": "^1.3.13" }, "devDependencies": { "@astrojs/check": "^0.9.6", diff --git a/apps/frontend/src/lib/sanitize.ts b/apps/frontend/src/lib/sanitize.ts index f1fb6bc..bb498b4 100644 --- a/apps/frontend/src/lib/sanitize.ts +++ b/apps/frontend/src/lib/sanitize.ts @@ -1,64 +1,41 @@ /** * SVG sanitization utilities * Prevents XSS attacks from user-provided SVG content + * + * Note: Uses basic regex-based sanitization for Cloudflare Workers compatibility + * DOMPurify doesn't work in Workers due to lack of DOM APIs */ -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 + * SVG sanitization - removes dangerous tags and attributes + * Suitable for Cloudflare Workers environment */ export const sanitizeSvg = (svg: string): string => { - // 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) - } + if (!svg || typeof svg !== 'string') { + return '' } - // Fallback for non-browser environments (Cloudflare Workers) - return basicSanitize(svg) + + let sanitized = svg + + // Remove script tags and their content + sanitized = sanitized.replace(/)<[^<]*)*<\/script>/gi, '') + + // Remove dangerous tags and their content + sanitized = sanitized.replace(/<(iframe|object|embed|style|link|meta|base)[^>]*>.*?<\/\1>/gi, '') + sanitized = sanitized.replace(/<(iframe|object|embed|style|link|meta|base)[^>]*\/?>/gi, '') + + // Remove event handlers (onclick, onload, onerror, etc.) + sanitized = sanitized.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, '') + sanitized = sanitized.replace(/\s+on\w+\s*=\s*[^\s>]+/gi, '') + + // Remove javascript: URLs + sanitized = sanitized.replace(/javascript\s*:/gi, '') + + // Remove data: URLs (except for images which are safe in SVGs) + sanitized = sanitized.replace(/(?