11// noinspection HtmlRequiredLangAttribute
22
33import { ExternalLink } from "lucide-react"
4- import { JSDOM } from "jsdom"
5- import createDOMPurify from "dompurify"
4+ import sanitizeHtml from "sanitize-html"
65
76interface ProjectPreviewProps {
87 url : string
@@ -30,14 +29,10 @@ export async function ProjectPreview({url, title, renderUrl}: ProjectPreviewProp
3029 if ( response . ok ) {
3130 let html = await response . text ( )
3231
33- // Use DOMPurify for proper HTML sanitization
34- const window = new JSDOM ( '' ) . window
35- const DOMPurify = createDOMPurify ( window as any )
36-
37- // Configure DOMPurify to be strict but allow necessary elements
38- html = DOMPurify . sanitize ( html , {
39- ALLOWED_TAGS : [
40- 'html' , 'head' , 'body' , 'title' , 'meta' , 'link' , 'style' ,
32+ // Server-side sanitization using sanitize-html (no jsdom/jsdom parse5 ESM issues)
33+ html = sanitizeHtml ( html , {
34+ allowedTags : [
35+ 'html' , 'head' , 'body' , 'title' , 'meta' , 'link' ,
4136 'div' , 'span' , 'p' , 'a' , 'img' , 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' ,
4237 'ul' , 'ol' , 'li' , 'table' , 'thead' , 'tbody' , 'tr' , 'td' , 'th' ,
4338 'header' , 'footer' , 'nav' , 'section' , 'article' , 'aside' , 'main' ,
@@ -47,25 +42,24 @@ export async function ProjectPreview({url, title, renderUrl}: ProjectPreviewProp
4742 'video' , 'audio' , 'source' , 'track' , 'canvas' , 'svg' , 'path' , 'circle' ,
4843 'rect' , 'line' , 'polyline' , 'polygon' , 'g' , 'text' , 'tspan'
4944 ] ,
50- ALLOWED_ATTR : [
51- 'href' , 'src' , 'alt' , 'title' , 'class' , 'id' , 'style' , 'width' , 'height' ,
52- 'data-*' , 'aria-*' , 'role' , 'rel' , 'target' , 'type' , 'name' , 'value' ,
53- 'placeholder' , 'disabled' , 'readonly' , 'checked' , 'selected' ,
54- 'colspan' , 'rowspan' , 'cellpadding' , 'cellspacing' , 'border' ,
55- 'viewBox' , 'd' , 'fill' , 'stroke' , 'stroke-width' , 'transform' ,
56- 'xmlns' , 'xmlns:xlink' , 'x' , 'y' , 'cx' , 'cy' , 'r' , 'rx' , 'ry' ,
57- 'x1' , 'x2' , 'y1' , 'y2' , 'points'
58- ] ,
59- FORBID_TAGS : [ 'script' , 'noscript' , 'object' , 'embed' , 'applet' , 'iframe' , 'base' ] ,
60- FORBID_ATTR : [ 'onerror' , 'onload' , 'onclick' , 'onmouseover' , 'onfocus' , 'onblur' ] ,
61- ALLOW_DATA_ATTR : true ,
62- ALLOW_ARIA_ATTR : true ,
63- WHOLE_DOCUMENT : true ,
64- RETURN_DOM : false ,
65- RETURN_DOM_FRAGMENT : false ,
66- SANITIZE_DOM : true ,
67- KEEP_CONTENT : true ,
68- IN_PLACE : false
45+ allowedAttributes : {
46+ '*' : [
47+ 'href' , 'src' , 'alt' , 'title' , 'class' , 'id' , 'width' , 'height' ,
48+ 'role' , 'rel' , 'target' , 'type' , 'name' , 'value' , 'placeholder' , 'disabled' , 'readonly' , 'checked' , 'selected' ,
49+ 'colspan' , 'rowspan' , 'cellpadding' , 'cellspacing' , 'border' ,
50+ 'viewBox' , 'd' , 'fill' , 'stroke' , 'stroke-width' , 'transform' ,
51+ 'xmlns' , 'xmlns:xlink' , 'x' , 'y' , 'cx' , 'cy' , 'r' , 'rx' , 'ry' ,
52+ 'x1' , 'x2' , 'y1' , 'y2' , 'points' ,
53+ 'data-*' , 'aria-*'
54+ ]
55+ } ,
56+ // Disallow potentially dangerous tags/attributes
57+ disallowedTagsMode : 'discard' ,
58+ // Allow data and blob URLs for images
59+ allowProtocolRelative : true ,
60+ allowedSchemesByTag : {
61+ img : [ 'http' , 'https' , 'data' , 'blob' ]
62+ }
6963 } )
7064
7165 // Remove existing CSP meta tags that might conflict
0 commit comments