Skip to content

Attribute-view column widths are stored without validation and interpolated into style attributes without escaping, allowing stored cross-site scripting in every table cell

Critical
88250 published GHSA-rj55-w3xr-gj62 Aug 1, 2026

Package

gomod github.com/siyuan-note/siyuan/kernel (Go)

Affected versions

master eef105683 and dev 98a4fb37b

Patched versions

v3.7.4

Description

Summary

The attribute-view store accepts an arbitrary string for a table column's width and applies no validation to it. Four frontend render sites interpolate that value directly into a style attribute with no escaping, including the template used for every table body cell. A quotation mark in the stored value terminates the attribute and injects an event handler.

Every other attribute in the same template is defended. Two of them are protected by exactly the kind of kernel-side validator this field lacks.

Details

The field is a free-form string. kernel/av/layout_table.go:65 declares Width string, and the setter applies no constraint at all:

// kernel/model/attribute_view.go:6216 (dev), :4681 (master)
column.Width = operation.Data.(string)

No length, character set, unit or CSS check. Confirmed end to end against kernel 3.7.4-alpha.2:

setAttrViewColWidth with data = 200px;x:y" onmouseover="alert(1)" data-x="
  → {"code":0}

renderAttributeView → width returned verbatim
on disk             → "width":"200px;x:y\"...

Four unescaped sinks.

  • app/src/protyle/render/av/render.ts:206 — column header
  • app/src/protyle/render/av/render.ts:216 and :219 — calculation footers
  • app/src/protyle/render/av/row.ts:224 — every table body cell

Present identically on master at 170, 181, 184 and 193.

The break-out is confirmed by parsing, not only by reading. Feeding the exact row.ts:218-225 template to a strict HTML parser, with width set to 200px" onmouseover="alert(1)" x=", produces this attribute list on the element:

style        = width: 200px
onmouseover  = alert(1)          ← a genuine event-handler attribute
x            = ;

The injected handler is a real attribute in the parsed DOM, not an inference from the template text.

Every neighbouring field is defended. Within the same HTML literal:

Field Protection
icon, desc escapeAttr() at render
name escapeHtml() at render
align Kernel-validated: align.IsValid(), else ErrInvalidColumnAlign
dateFormat Kernel-validated: format.IsValid(), else an error
width Neither

Two of the immediate neighbours already carry the kernel-side validator pattern that this field needs.

Not addressed by the recent fix. Commit 63e60bc4b modified kernel/model/attribute_view.go, but only the colour call sites. column.Width = operation.Data.(string) remains at dev HEAD 98a4fb37b, and all four render sites remain unescaped.

Consequence in the desktop application. app/electron/main.js sets nodeIntegration: true, contextIsolation: false and webSecurity: false on every window (lines 913, 1019-1022, 1933-1936, 2157, 2197, 2248), so script executing in a renderer reaches Node built-ins including require('child_process').

Scope of observation. The storage behaviour, the API response, the four interpolation sites and the resulting parsed attribute list are all confirmed as described. I have not observed handler invocation in a running client, so the final step from the injected attribute to execution is stated from the parse result and the Electron configuration rather than from a captured runtime event.

Two adjacent paths I checked and excluded, so they need no attention:

  • row.ts:437, style="width: ${item.style.width}", reads its value back through CSSOM, which normalises the quotation mark away.
  • relation.ts:394 and :414, grid-template-columns:${gridTemplate}, appear identical but getRelationGridTemplate returns only the literals 32px, 240px, 200px and 160px, so no attacker input reaches them.

Relationship to existing advisories. column.width, colWidth, setAttrViewColWidth, av__widthdrag, style="width, grid-template and av__calc each appear in no published advisory for this project. The only match on the substring width: is GHSA-x63q-* (Mermaid javascript: links, CVE-2026-40322), an incidental occurrence inside a style snippet.

Proof of Concept

Set a table column's width to a value containing a quotation mark:

POST /api/transactions
{"reqId":<numeric>, ... "action":"setAttrViewColWidth",
 "data":"200px;x:y\" onmouseover=\"alert(1)\" data-x=\""}

→ {"code":0}

Confirm it is stored and served unmodified:

POST /api/av/renderAttributeView
→ width returned verbatim, quotation marks intact

Then open any table view of that database.

Impact

A stored value under attacker control breaks out of the style attribute on every cell of a table column, injecting an event handler. Because row.ts:224 is the body-cell template, the injection is present on every row rather than on a single header element, and it renders on opening the view rather than on any deliberate interaction.

In the desktop client the renderer has Node integration enabled, so execution at that point is not confined to the page.

The value is written through the ordinary column-resize operation, so any path by which a database reaches a victim, including import, synchronisation, a shared workspace or a distributed package, carries the payload.

Suggested fix

Two changes:

  1. Apply escapeAttr to the width value at all four render sites.
  2. Add a FilterWidthValue in the kernel, mirroring the FilterColorValue introduced in 63e60bc4b, accepting only an empty string or a value matching ^\d+(\.\d+)?(px|em|rem|%)$. Apply it in both setAttributeViewColWidth and the bulk setAttributeViewColsWidth.

The second is the durable half, and it matches the treatment align and dateFormat already receive in the same structure. It also neutralises any value already stored.

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
Required
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H

CVE ID

No known CVE

Weaknesses

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

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. Learn more on MITRE.

Credits