Clean SVG icons before preview/upload client-side (#7831)

* custom icons * fix: ensures svg are sanitized front side on upload/preview * fix: restore default sidebar icons * chore: remove unrelated icon files --------- Co-authored-by: Sammy Ndabo <sammy.ndabo@evoludata.com>

Sammy Ndabo committed May 26, 2026 at 17:58 UTC c0ca2406cf088f7812da1255142a2d530fd0f34a
1 file changed +10 -2
public/js/ui-components.js
+10 -2
@@ -311,12 +311,20 @@ class IconUploadComponent {
311 try {
312 if (!(/^image\/(svg\+xml|png|jpeg)$/i.test(file.type)) && !(/\.(svg|png|jpg|jpeg)$/i.test(file.name || ''))) { throw new Error('Only SVG, PNG and JPEG icon files are supported.'); }
313 if ((file.size < 4) || (file.size > CUSTOM_ICON_MAX_FILE_SIZE)) { throw new Error('Icon files must be non-empty and ' + (CUSTOM_ICON_MAX_FILE_SIZE / 1048576) + ' MB or smaller.'); }
314 - if (!(/\.(svg)$/i.test(file.name || '') || /^image\/svg\+xml$/i.test(file.type))) {
314 + var uploadFile = file;
315 + if (/\.(svg)$/i.test(file.name || '') || /^image\/svg\+xml$/i.test(file.type || '')) {
316 + // Ensure DOMPurify is loaded
317 + if ((typeof DOMPurify === 'undefined') || (typeof DOMPurify.sanitize !== 'function')) { throw new Error('Unable to clean SVG icon in this browser.'); }
318 + // Clean SVG file
319 + const cleanedSvg = DOMPurify.sanitize(await file.text(), { USE_PROFILES: { svg: true, svgFilters: true } });
320 + if ((typeof cleanedSvg !== 'string') || (cleanedSvg.search(/<svg[\s>]/i) < 0)) { throw new Error('Invalid SVG icon file.'); }
321 + uploadFile = new File([cleanedSvg], file.name, { type: 'image/svg+xml', lastModified: file.lastModified });
322 + } else {
323 const dimensions = await this.getImageDimensions(file);
324 if ((dimensions.width < 1) || (dimensions.height < 1) || (dimensions.width > CUSTOM_ICON_MAX_DIMENSION) || (dimensions.height > CUSTOM_ICON_MAX_DIMENSION)) { throw new Error('PNG/JPEG icon images must be ' + CUSTOM_ICON_MAX_DIMENSION + ' x ' + CUSTOM_ICON_MAX_DIMENSION + ' pixels or smaller.'); }
325 }
326 if (this.options.onUpload) {
319 - const result = await this.options.onUpload(this.iconKey, file);
327 + const result = await this.options.onUpload(this.iconKey, uploadFile);
328
329 // Show success state
330 button.innerHTML = '<i class="fas fa-check me-2"></i>Success!';