@@ -4,7 +4,7 @@ import {h} from "preact";
44/**
55 * Icon component
66 * Renders an SVG icon from a raw SVG string passed from Hugo.
7- * Decodes JSON-escaped sequences (\u003c) and HTML entities (<, ", ") .
7+ * Defensively decodes any residual JSON unicode escapes and HTML entities.
88 *
99 * SVG sizing strategy:
1010 * - By default, icons size to 1em (matching current font size) — works universally
@@ -16,18 +16,19 @@ import {h} from "preact";
1616export const Icon = ( { svg, attributes} ) => {
1717 if ( ! svg ) return null ;
1818
19- // Clean the SVG string: decode HTML entities and TRIM whitespace
19+ // Decode any residual JSON unicode escapes (e.g. \u003c → <) and HTML entities
20+ // (e.g. < → <) that survive the JSON.parse + DOM read pipeline.
21+ // Uses the browser's built-in HTML parser rather than hand-rolled regexes —
22+ // this is inherently safe against double-unescaping (CWE-116).
2023 let decoded = String ( svg )
21- . replace ( / \\ u 0 0 3 c / gi, "<" )
22- . replace ( / \\ u 0 0 3 e / gi, ">" )
23- . replace ( / & l t ; / gi, "<" )
24- . replace ( / & g t ; / gi, ">" )
25- . replace ( / & q u o t ; / gi, '"' )
26- . replace ( / & # 3 4 ; / gi, '"' )
27- . replace ( / \\ u 0 0 2 6 / gi, "&" )
28- . replace ( / & a m p ; / gi, "&" )
24+ . replace ( / \\ u ( [ 0 - 9 a - f A - F ] { 4 } ) / g, ( _ , hex ) => String . fromCharCode ( parseInt ( hex , 16 ) ) )
2925 . trim ( ) ;
3026
27+ // Let the browser's HTML parser handle all entity decoding in one safe pass
28+ const _textarea = document . createElement ( "textarea" ) ;
29+ _textarea . innerHTML = decoded ;
30+ decoded = _textarea . value ;
31+
3132 const hasWrapper = / < s v g [ \s > ] / i. test ( decoded ) ;
3233
3334 if ( hasWrapper ) {
0 commit comments