Skip to content

File Browser proxy-auth, SiYuan local-app, Authorizer OAuth, and Pimcore CMS boundary checks

Source: hourly offensive-security scan, 2026-07-10 late GitHub advisory wave with 19:32 and 20:37 UTC follow-ups. Primary entries: GHSA-xqp3-jq6g-x3qm / CVE-2026-54089, GHSA-m93h-4hw7-5qcm / CVE-2026-54088, GHSA-hvr9-72v2-fff3 / CVE-2026-54069, GHSA-gcm7-57gf-953c / CVE-2026-54068, GHSA-p4m3-mgmm-c664 / CVE-2026-54066, GHSA-w7cg-whh7-xp28 / CVE-2026-54070, GHSA-mvjr-vv3c-w4qv / CVE-2026-54067, GHSA-5xfx-xj4h-5p7r / CVE-2026-54158, GHSA-56mp-4f3v-fgj2 / CVE-2026-50551, GHSA-h29v-hj44-q8cv / CVE-2026-54072, and the Pimcore updates GHSA-wc7j-g8wx-m2qx, GHSA-r2f4-ff2p-xc64, GHSA-jwcc-gv4m-93x6, GHSA-36fc-7wjg-mfvj, GHSA-332x-r494-54fq, GHSA-h4ph-crvj-9h92, and GHSA-3234-gxc3-pq6f.

This batch is durable because each item maps to a repeatable operator boundary: a reverse-proxy identity header reaching an app that is also directly exposed, desktop/browser origins crossing into loopback note-app administrator APIs, unauthenticated template helpers crossing into local SQLite reads, publish-mode path normalization missing a second decode, marketplace or synced content crossing into trusted desktop rendering, OAuth implicit-flow redirect URIs missing client allowlist checks, and CMS backend feature permissions drifting away from object-level permissions, SQL construction, or deserialization sinks.

Authorized validation only

Keep proofs to disposable File Browser, SiYuan, Authorizer, and Pimcore labs. Use fake proxy usernames, synthetic note blocks, marker-only workspace files, harmless DOM markers, owned redirectors, disposable OAuth clients/users, seeded CMS assets/reports/documents, and local database canaries. Do not impersonate real users, query personal notes, read workspace secrets, capture live OAuth tokens, move/delete production assets, dump real CMS data, or run PHP gadget chains.

What changed

Advisory Component Boundary Operator value
GHSA-xqp3-jq6g-x3qm File Browser proxy auth Direct requests can supply the trusted proxy identity header when auth.method=proxy is enabled and the backend is network-reachable Add direct-backend exposure and header-forgery checks to File Browser/reverse-proxy assessments.
GHSA-m93h-4hw7-5qcm File Browser hook auth Login-screen username/password values are expanded into an external shell hook command before authentication Add hook-auth command-construction tests with inert credential canaries to File Browser reviews.
GHSA-hvr9-72v2-fff3 SiYuan kernel auth Any chrome-extension:// origin is treated as administrator for loopback APIs Test local desktop app APIs for blanket browser-extension origin trust and missing extension-ID binding.
GHSA-gcm7-57gf-953c SiYuan dynamic icon API Unauthenticated icon rendering executes template helpers that can read SQLite note data when a valid block ID is supplied Add unauthenticated render/template helper reachability checks with synthetic block IDs and marker tables.
GHSA-p4m3-mgmm-c664 SiYuan publish /assets/*path First-pass URL cleanup is followed by a fallback decode and publish access fall-through outside DataDir Test double-encoding and route-specific patch gaps using only workspace canary files.
GHSA-w7cg-whh7-xp28 SiYuan Bazaar README renderer Markdown sanitizer blocks a legacy event-handler list but allows newer handler attributes before innerHTML insertion Treat marketplace README/rendered-package metadata as trusted-origin DOM input; prove with harmless event markers.
GHSA-mvjr-vv3c-w4qv SiYuan snippets CSS snippet content can break out of a generated <style> tag and reach the Electron renderer Test synced workspace configuration as a desktop-renderer trust boundary, without host command payloads.
GHSA-5xfx-xj4h-5p7r SiYuan attribute-view cells Synced database cell values in text, url, phone, and mAsset branches are interpolated into renderer HTML without escaping Add attribute/database cell renderers to synced-workspace DOM-sink checks; stop at harmless renderer canaries unless a lab explicitly permits inert Electron sink validation.
GHSA-56mp-4f3v-fgj2 SiYuan asset cells Attribute View asset link names/content and tooltip aria-label round-trips reach innerHTML, including immediate page-load and hover-triggered sinks Expand Attribute View tests to direct asset-cell page-load rendering and hover tooltip paths, not just text/url/phone branches.
GHSA-h29v-hj44-q8cv Authorizer /authorize redirect_uri is not validated against allowed origins before implicit-flow tokens are appended and redirected Reuse OAuth redirect allowlist tests with owned callback URLs and fake lab tokens.
Pimcore advisory wave Pimcore CMS/backend WebDAV moves, report detail/export routes, class-definition import, translation filters, custom report SQL, and deserialization sources miss the expected auth/object/schema boundary Add CMS route-family drift, object-permission, SQL-construction, and chained-deserialization precondition checks to Pimcore tests.

Replayable validation boundaries

File Browser proxy-auth header forgery

  1. Confirm the deployment explicitly uses File Browser auth.method=proxy; default JSON/password auth is a negative control.
  2. Determine whether the File Browser backend is reachable directly, bypassing the trusted reverse proxy. Check only approved network paths and record listener address, security group/load-balancer path, and container port publishing.
  3. In a lab, send a request directly to the backend with the configured proxy username header, using a fake account such as fb-canary-user or a disposable admin account.
  4. Record whether File Browser accepts the header as identity or auto-creates the user. Evidence should be a role/session decision table, not access to real files.
  5. Add a negative control through the intended proxy path where the proxy strips client-supplied identity headers before setting its own value.

Report this as direct backend reachability -> caller-supplied proxy identity header -> File Browser session/user creation. Do not target production user names, shares, files, or admin actions.

File Browser hook-auth command construction

  1. Confirm hook authentication is enabled and identify the exact external command template. Default JSON/password auth is a negative control.
  2. In a disposable lab, submit login attempts where username and password contain inert shell metacharacter canaries that should be treated as data, not command syntax.
  3. Instrument the hook wrapper to log argv, environment, and marker-file side effects. Positive evidence is command substitution, extra shell tokens, or a marker generated before any successful authentication.
  4. Compare with a patched or safer configuration that passes credentials as fixed argv/environment values and rejects shell evaluation.

Report this as pre-auth login credential -> hook command expansion -> shell command boundary. Do not run reverse shells, destructive commands, or production hook binaries; keep evidence to inert marker strings and wrapper logs.

SiYuan browser-extension and loopback administrator boundary

  1. Use a disposable SiYuan desktop or Docker lab with synthetic notebooks and no personal workspace data.
  2. From a test Chromium extension you control, make one harmless API request to the loopback kernel and record the Origin: chrome-extension://<id> value, response status, and reported role.
  3. Test whether authorization binds to a specific extension ID, configured token, AccessAuthCode, or browser profile. A blanket scheme allow is the vulnerable pattern.
  4. Keep proof to route metadata or a synthetic configuration canary. Do not query note content, assets, sync keys, tokens, or browser history.

Report as browser extension origin -> blanket local API trust -> administrator role on loopback app. This is a local-service browser-pivot pattern; pair it with scoped extension and localhost reachability evidence.

SiYuan unauthenticated render helpers and publish path traversal

  1. Seed a lab workspace with one synthetic block ID and a marker row or note whose content is safe to disclose, for example SIYUAN-CANARY-ONLY.
  2. Exercise /api/icon/getDynamicIcon with a template that queries only the seeded canary. Positive evidence is the canary in the SVG/response body without authentication.
  3. For publish mode, place a harmless marker file under a lab workspace path that is outside DataDir but not sensitive.
  4. Compare single-encoded, double-encoded, and patched route behavior on /assets/*path and any sibling routes that recently received traversal fixes.
  5. Stop at marker files. Never request conf, database, log, sync, token, or personal note files.

Report the boundaries separately: unauthenticated render endpoint -> template SQL helper -> note database canary, and double-encoded route parameter -> second decode fallback -> publish-mode outside-DataDir file read.

SiYuan marketplace README and snippet renderer DOM sinks

  1. For Bazaar README rendering, create a lab package or mocked marketplace response with harmless modern event-handler attributes such as pointer/focus/animation canaries.
  2. Open the package listing as a disposable administrator after the explicit marketplace trust step, and capture whether the rendered README is assigned into the main document without sandboxing.
  3. For snippet rendering, create a synced workspace snippet whose CSS body contains a harmless </style> breakout marker that changes a local DOM marker only.
  4. For attribute-view/database cells, seed a synced lab workspace with harmless values in text, url, phone, and mAsset branches that attempt only DOM marker insertion or a benign console.log canary when the block-attribute panel opens. Include asset-cell link names/content that render immediately on page load and aria-label tooltip values that render only after hover.
  5. On desktop/Electron builds, do not use host-command payloads. The proof should stop at renderer script execution and pre/post DOM marker values unless the program explicitly approves an inert Electron sink such as writing to a temp canary file.
  6. Include controls where sanitization allowlists attributes, escapes </style>, HTML-escapes cell values, uses a sandboxed iframe, or renders text inertly.

Report as untrusted marketplace/synced content -> trusted SiYuan origin DOM insertion -> renderer script canary. Keep host RCE discussion conditional unless an approved lab explicitly validates the Electron sink with inert commands.

Authorizer OAuth redirect URI token relay

  1. Create a disposable Authorizer lab with a fake client, fake user, and owned callback domain.
  2. Pull only public client metadata that the target intentionally exposes, then build /authorize requests for response_type=token or id_token with an unregistered redirect_uri under your owned domain.
  3. Authenticate as the disposable user and record only whether the 302 Location points to the unregistered callback and contains fake token parameters. Redact token bodies or use throwaway tokens.
  4. Test the expected negative controls: registered redirect succeeds, unregistered redirect is rejected, and other login/email flows that were previously fixed still call the allowlist validator.

Report this as public client ID -> unvalidated redirect_uri in /authorize -> implicit-flow tokens relayed to attacker-controlled origin. Do not target real users, harvest tokens, or send phishing links.

Pimcore CMS route, SQL, and deserialization drift

  1. Build a Pimcore lab with two backend users, seeded assets/documents/reports, and marker-only database rows.
  2. For WebDAV, test whether unauthenticated or low-privileged MOVE can move/delete/overwrite only disposable assets when normal asset permissions would deny it.
  3. For CustomReports and WordExport, compare list visibility to direct detail/export routes using synthetic report/document IDs. Positive evidence is a hidden canary returned from a route whose listing correctly hid it.
  4. For class-definition import/save, translation grid filters, and custom report column configuration, keep SQL proofs to harmless syntax, error, or selected marker values. Do not dump user tables or credentials.
  5. Treat unsafe deserialization as a chain precondition. Show only that a seeded serialized canary reaches an unserialize() source; do not run gadget chains or publish executable payloads.

Report the exact boundary: WebDAV route -> unauthenticated asset move, feature permission -> object export without view permission, report listing filter -> direct report detail bypass, class/report/filter metadata -> SQL expression construction, or writable CMS state -> unrestricted PHP deserialization sink.

October 1 follow-up: SiYuan publish-mode SQL injection and admin-layout disclosure (2 GHSAs)

GHSA-33jq-p8c2-q3q4 (critical): in publish mode, /api/filetree/searchDocs splices the caller-supplied keyword k straight into single-quoted LIKE literals across SearchDocs, NAMFilter (name/alias/memo branches, on by default), and QueryRootBlockByCondition — no escaping, no bind placeholders. The statement runs on a read-write SQLite handle through a driver that executes stacked ;-separated statements, against the global blocks table spanning every cleartext notebook. The route is gated only by CheckAuth, which the publish RoleReader token satisfies — and which is skipped entirely when Publish.Auth.Enable is false, making this unauthenticated. The only value-inspecting guard is ast.IsNodeIDPattern(), a shape check on a different input field. Proof with synthetic lab notebooks: one stacked SELECT reading a marker row from a second notebook, then a patched-commit (eef10568384e+) negative control; never target real user databases.

GHSA-hgfg-j9pg-43xw / CVE-2026-72788: POST /api/system/getConf is likewise reachable by the publish reader token (anonymously when auth is off) and returns Conf.UILayout through FilterConfByPublishIgnore — a walker whose four defects let an argument-less POST return the administrator's open password-protected document titles/IDs, locked/closed notebook contents, recent search terms with their scoped paths, and open private asset paths. Key asymmetry: the writer (setUILayout) is admin-gated, so the reader is replaying the admin's live workspace state, re-saved on every tab change. Restructuring the HideConfSecret blocklist path would not fix this — UILayout is a field intended to be served and filtered, and the filter's coverage is the bug.

Operator value: one reusable rule for publish/share modes in desktop-first apps — enumerate every API route the low-privilege reader token can hit, then check each response for admin-derived state (layout, recent items, drafts, search history) that the field-level filter was written to cover one known field at a time. Filters over allowlisted shapes fail open on every new field added to the payload; the stacked-statement SQLi additionally shows publish mode inherits the full read-write DB handle, so any string-concatenated query fragment behind the reader gate is a cross-notebook write primitive. Validation stays lab-only: disposable published instance, synthetic notebooks with marker rows/strings, reader-token route decision table (route, token, expected redaction, observed fields), owned-commit patched controls.

Reporting notes

  • Lead with deployment preconditions: proxy-auth mode and direct backend reachability for File Browser; desktop/loopback, publish mode, valid block IDs, marketplace trust, or synced workspace write access for SiYuan; implicit-flow route exposure for Authorizer; authenticated backend role and feature permissions for Pimcore.
  • Prefer decision tables over exploit prose: raw route, normalized route, role, expected denial, observed response, canary returned, and patched/negative control.
  • Redact proxy usernames, OAuth tokens, workspace paths, block IDs tied to real notes, CMS object IDs, report names, database errors that reveal schema beyond the canary, and any host/container paths that identify tenants.
  • Skip adjacent resource-only advisories from the same wave unless they unlock a non-availability operator workflow in scope.

October 2 follow-up: SiYuan publish-mode reader-scope leaks (3 GHSAs, 12:31Z wave, folded)

Three more publish-mode advisories (fixed in 3.8.5) extend the October 1 reader-token route-matrix rule with what the leak endpoints look like:

  • /api/export/preview reads protected database rows (CVE-2026-104410 / GHSA-84pg-x94v-x3xr, 7.5): request an export preview of a public document that embeds a database view, and password-protected / publish-disabled rows come back with primary-key text and cell values. The publish gate checked the document, not the embedded data source the document references — every embed/reference resolution is a second authorization decision that publish mode must make separately. Test: publish one disposable doc that embeds a protected attribute-view, then preview-export it as the reader.
  • getNotebookInfo leaks hidden-document metadata (CVE-2026-103763 / GHSA-cj7w-46vh-93h9, 5.8, anonymous when no reader password): document count, size, and modification timestamps of publish-excluded documents in publish-visible notebooks. Existence/metadata of excluded items still flows through notebook-level endpoints.
  • getRefCreateSavePath / getShorthandSavePath / getDocCreateSavePath reveal unpublished notebook internals (CVE-2026-103762 / GHSA-7234-66p2-jh2w, 5.3): POST any open notebook ID and receive the global save-box ID and save-path template for hidden notebooks — notebook existence and creation time disclosed via create-path helpers that were never meant to be publish-reachable. "Save path" and "where would this be created" endpoints leak structure because they're nominally write-support routes, not read routes; include them in the reader-token route sweep anyway.

Operator delta over the October 1 rule: the route sweep must include export/preview helpers and create-path/save-path utilities, not just read endpoints — reference resolution (embedded databases), notebook-level metadata aggregation, and path-template responses each made an independent leak surface inside the same reader gate.

October 2 22:3xZ follow-up: SiYuan publish-mode bulk handler sweep + admin WS proxy CSWSH (folded from the 22:3xZ wave)

  • 17 of 28 /api/block/* handlers have zero publish-access filtering (GHSA-4vpg-gwqq-w44c / CVE-2026-74904, 7.5): the researcher's method is the lesson — automated extraction of every function body in kernel/api/block.go, grep for any of the three access-check symbols, cross-check against sibling functions in the same file that implement the check correctly. Result: getRefText returns the reference-display text (content-derived) for any block ID to anonymous publish-mode readers, checkBlockExist/checkBlocksExist are existence oracles, getBlockBreadcrumb leaks title paths, plus index/word-count/fold/sibling/annotation lookup endpoints — 17 handlers gated only by CheckAuth with no publish check anywhere in the handler or its model functions. This extends the reader-token route sweep from export/preview helpers (Oct 2 fold above) to the whole file: when a feature gate exists in some handlers of a router file, the correct audit is a per-function symbol sweep of that file, not spot checks — uneven coverage is the norm.
  • Admin-only network-proxy WebSocket hard-codes CheckOrigin: true (GHSA-3cc2-h3v6-rqpq / CVE-2026-74802, title CVSS low, advisory self-assessed high; unfixed at 3.7.3): /ws/network/proxy is an admin-role, fully attacker-specifiable forward proxy (base64 target URL + headers via query params) whose gorilla/websocket upgrader deliberately overrides the library's default origin check to always-true. WS handshakes carry ambient cookies and skip CORS preflight entirely → a malicious webpage gets an authenticated network pivot through the kernel process (loopback-bound desktop app = CSRF-through-the-user's-browser against a localhost-only service). Same day as the Headroom CSWSH leg on the Oct 2 AI page — durable pair: grep websocket upgraders for CheckOrigin overrides returning true, and treat any proxy/pivot route as CSWSH-grade regardless of its role gate, because the role gate travels on cookies the browser attaches anyway.

October 2 follow-up: SiYuan AI/MCP agent-plane gaps — rebinding TOCTOU + unbounded upload tool (2 GHSAs, folded)

The SiYuan advisory stream moved from the publish-mode reader plane to the AI agent/MCP tool plane (both incomplete-fix residuals of the earlier MCP/SSRF remediations, fixed 3.8.1):

  • Agent http_request/web_fetch SSRF via DNS-rebinding TOCTOU (GHSA-x8gv-g2g3-65fj, 8.2, ≤3.8.0): the SSRF gate CheckHostSSRF performs one DNS resolution at guard time and rejects private/loopback/link-local answers — then the actual request connects through the default dialer, which performs a second, independent resolution with no connect-time check. Guard-answer public IP, connect-answer 169.254.169.254. The remediation added both halves (CheckHostSSRF parse-time + SSRFSafeDialer connect-time) but only wired the parse-time half into the agent tool paths — the main-vector fix exists in the same repo; the new tool surface just didn't call it. Operator rule: when a product ships a hardened dialer/socket helper, enumerate which fetch call sites actually use it; any guard that resolves DNS without pinning the answer to the connection (guard-time resolve + unpinned dial = TOCTOU by construction) is rebinding-testable with an owned short-TTL DNS fixture. This is the agent-tooling spelling of the classic rebinding pattern: prompt-injecting the agent is the delivery leg, and the advisory notes the tool's own docs document that scenario.
  • MCP asset.upload reads arbitrary absolute paths (GHSA-p23f-cm6q-2qp8, 5.7, ≤3.8.0): the tool's files argument is a comma-separated list of absolute paths, normalized with only filepath.Abs() — no IsSubPath workspace check, no IsSensitivePath check — and InsertLocalAssets os.Opens each and copies the bytes into the workspace assets/ directory, from where notes/export/sync can reach them. Every sibling file tool (file, unzip) resolves through the escape-proof resolvePath; asset.upload is the one tool the MCP hardening pass (CVE-2026-66012, which added CheckAdminRole on /mcp and refuseToAccess to the file tool) never touched. Operator rule: when auditing an agent tool registry, diff boundary-check symbol coverage per tool handler, not per server — the same per-function symbol sweep that found the 17/28 block.go publish gaps above applies to kernel/mcp/tools/: grep every tool handler for the workspace/sensitive-path predicates and treat any tool missing them as the residual. The copy-into-workspace sink is also the exfil pattern to hunt in any agent file tool: a tool that copies attacker-named files into an agent-readable/syncable location converts a local read into remote disclosure without any network egress from the host.

Validation stays lab-only: disposable SiYuan 3.8.0 build, owned domain with TTL-0/round-robin DNS answering public→loopback for the rebinding leg (prove with a synthetic canary served on lab loopback, never cloud metadata or third-party internals), and a temp canary file outside the workspace copied by a prompted asset.upload call; patched 3.8.1 controls on both legs.

October 5 follow-up: SiYuan path-guard residuals — blocklist content gaps + unguarded snapshot routes (2 GHSAs, folded)

The same util.IsForbiddenAbsPath() guard introduced by the GHSA-c8r8-95hg-mp34 remediation produced two more residuals in the Oct 5 17:32Z wave, each exposing a different failure dimension of a centralized deny-list:

  • Deny-list content is a snapshot; key material generated at boot is not in it (GHSA-4wwp-f6gw-6qm5): the blocklist covers conf/conf.json by exact match, but conf/key.pem and conf/ca.key sit in the same directory and were never added. POST /api/file/getFile {"path":"/conf/key.pem"} returns raw PEM private-key bytes — and on v3.8.1 every authenticated user receives RoleAdministrator (no non-admin role is issued to direct API consumers), and on default no-auth-code instances no token is required at all. The files are generated on every boot even when TLS is inactive, so the secret exists on every installation. Impact chain: key.pem → decrypt captured HTTPS traffic; ca.key → mint certs trusted by clients that imported SiYuan's locally-generated CA. Third occurrence of this exact family (with GHSA-9jfx-rc58-h23j conf.json-via-template-render and GHSA-c8r8 MCP-tool blocklist). Operator rule: when a product's sensitive-file guard is a deny-list, enumerate the sibling files of each blocked path — directory listings, boot-time key/cert generation, and config writers add secrets the deny-list author never enumerated; and grep for role grants that make "authenticated" equal "administrator", because it silently downgrades every authenticated-user-reads-X advisory to unauthenticated-on-default-config.
  • Guard application missed sibling file-serving routes, and historical copies dodge the guard anyway (GHSA-3cm4-ccvw-6xr6, 4.9): the fix wired IsForbiddenAbsPath() into kernel/api/file.go and kernel/mcp/tools/file.go — but /history/*path and /repo/diff/*path in kernel/server/serve.go also serve arbitrary files by path, construct their target paths independently, and never call the guard. Because the history/repo tracked root is data/, a prior snapshot of data/.siyuan/publishAccess.json (plaintext publish passwords) or data/templates/* legitimately sits inside util.HistoryDir / the repo-diff checkout — /repo/diff/*path's own ../IsSubPath checks only confine you within the checkout, which is exactly where the protected file's historical copy lives. Two durable axes: (1) when a fix adds a centralized guard symbol, grep every route that serves files by path and check each call site — sibling routes that build paths independently are the residual (same per-call-site sweep as the 17/28 block.go finding above, applied to fix rollout instead of feature audit); (2) snapshot/history/backup/checkout directories are a bypass plane over any live-path protection — the guard protects where the file is, not where copies of it were; sweep history, diff, autosave, sync-temp, and backup endpoints for every recently-protected path.

Validation stays lab-only: disposable SiYuan instance, request conf/ sibling paths only on your own lab install (redact any real PEM material from evidence), seed then delete a synthetic publishAccess.json-style marker so a history snapshot exists, then fetch it via /history/ and /repo/diff/ with patched-version negative controls; never capture real user key material or publish passwords.

October 12 follow-up: SiYuan kernel plugin-response absolute-path read + uncapped Sprig function map in dynamic-icon endpoint (fixed 3.8.6)

  • Plugin handler ResponseFile.Path reaches gin Context.File unvalidated (GHSA-g3cr-c99q-2hfc/CVE-2026-109309, medium): a kernel plugin (installable from the community marketplace) returns a file-serving response whose Path is used verbatim, so a malicious kernel plugin reading any file the SiYuan process can read — including the API token and conf.json — through the private plugin HTTP server. The boundary this page already documents for the server→filesystem direction now breaks in the plugin→server→response direction: plugin-supplied file paths are attacker-supplied file paths; confinement must be re-applied at the serving sink, not assumed at the handler. Supply-chain angle: marketplace plugins are the delivery vehicle, so the plugin surface is worth its own review pass on any SiYuan deployment in authorized scope.
  • /api/icon/getDynamicIcon content param passes an unrestricted Sprig template function map (GHSA-q79h-f459-w43f/CVE-2026-109310, high): uncapped helpers like until let a publish reader (or anyone when publish auth is off) OOM-kill the kernel in one request. Template-engine resource rule: template params must run against an allow-listed, capped function map; Sprig's full map is a compute-DoS surface (until, repeat families) even without RCE-capable funcs. One-request kernel death from the publish tier is also a availability signal for the publish-attack-surface inventory on this page.