@@ -36,6 +36,28 @@ const Thumbnails = ({ images, style, setImages, onHeightChange }: ThumbnailsProp
3636 vscode . postMessage ( { type : "openImage" , text : image } )
3737 }
3838
39+ // Sanitize image URL to prevent XSS and malicious redirects
40+ const sanitizeImageUrl = ( url : string ) : string => {
41+ try {
42+ // Only allow data URLs (base64 images) and https URLs
43+ if ( url . startsWith ( "data:image/" ) ) {
44+ return url
45+ }
46+
47+ // For other URLs, validate they are safe
48+ const parsedUrl = new URL ( url )
49+ if ( parsedUrl . protocol === "https:" || parsedUrl . protocol === "http:" ) {
50+ return url
51+ }
52+
53+ // Reject any other protocols (javascript:, file:, etc.)
54+ return ""
55+ } catch {
56+ // Invalid URL, return empty string
57+ return ""
58+ }
59+ }
60+
3961 return (
4062 < div
4163 ref = { containerRef }
@@ -46,51 +68,59 @@ const Thumbnails = ({ images, style, setImages, onHeightChange }: ThumbnailsProp
4668 rowGap : 3 ,
4769 ...style ,
4870 } } >
49- { images . map ( ( image , index ) => (
50- < div
51- key = { index }
52- style = { { position : "relative" } }
53- onMouseEnter = { ( ) => setHoveredIndex ( index ) }
54- onMouseLeave = { ( ) => setHoveredIndex ( null ) } >
55- < img
56- src = { image }
57- alt = { `Thumbnail ${ index + 1 } ` }
58- style = { {
59- width : 34 ,
60- height : 34 ,
61- objectFit : "cover" ,
62- borderRadius : 4 ,
63- cursor : "pointer" ,
64- } }
65- onClick = { ( ) => handleImageClick ( image ) }
66- />
67- { isDeletable && hoveredIndex === index && (
68- < div
69- onClick = { ( ) => handleDelete ( index ) }
71+ { images . map ( ( image , index ) => {
72+ const sanitizedUrl = sanitizeImageUrl ( image )
73+ // Skip rendering if URL is invalid/unsafe
74+ if ( ! sanitizedUrl ) {
75+ return null
76+ }
77+
78+ return (
79+ < div
80+ key = { index }
81+ style = { { position : "relative" } }
82+ onMouseEnter = { ( ) => setHoveredIndex ( index ) }
83+ onMouseLeave = { ( ) => setHoveredIndex ( null ) } >
84+ < img
85+ src = { sanitizedUrl }
86+ alt = { `Thumbnail ${ index + 1 } ` }
7087 style = { {
71- position : "absolute" ,
72- top : - 4 ,
73- right : - 4 ,
74- width : 13 ,
75- height : 13 ,
76- borderRadius : "50%" ,
77- backgroundColor : "var(--vscode-badge-background)" ,
78- display : "flex" ,
79- justifyContent : "center" ,
80- alignItems : "center" ,
88+ width : 34 ,
89+ height : 34 ,
90+ objectFit : "cover" ,
91+ borderRadius : 4 ,
8192 cursor : "pointer" ,
82- } } >
83- < span
84- className = "codicon codicon-close"
93+ } }
94+ onClick = { ( ) => handleImageClick ( image ) }
95+ />
96+ { isDeletable && hoveredIndex === index && (
97+ < div
98+ onClick = { ( ) => handleDelete ( index ) }
8599 style = { {
86- color : "var(--vscode-foreground)" ,
87- fontSize : 10 ,
88- fontWeight : "bold" ,
89- } } > </ span >
90- </ div >
91- ) }
92- </ div >
93- ) ) }
100+ position : "absolute" ,
101+ top : - 4 ,
102+ right : - 4 ,
103+ width : 13 ,
104+ height : 13 ,
105+ borderRadius : "50%" ,
106+ backgroundColor : "var(--vscode-badge-background)" ,
107+ display : "flex" ,
108+ justifyContent : "center" ,
109+ alignItems : "center" ,
110+ cursor : "pointer" ,
111+ } } >
112+ < span
113+ className = "codicon codicon-close"
114+ style = { {
115+ color : "var(--vscode-foreground)" ,
116+ fontSize : 10 ,
117+ fontWeight : "bold" ,
118+ } } > </ span >
119+ </ div >
120+ ) }
121+ </ div >
122+ )
123+ } ) }
94124 </ div >
95125 )
96126}
0 commit comments