Skip to content

SiYuan: Publish Reader Path Traversal Delete via `removeUnusedAttributeView`

High severity GitHub Reviewed Published Apr 10, 2026 in siyuan-note/siyuan • Updated Apr 10, 2026

Package

gomod github.com/siyuan-note/siyuan/kernel (Go)

Affected versions

< 0.0.0-20260407035653-2f416e5253f1

Patched versions

3.6.40.0.0-20260407035653-2f416e5253f1

Description

SiYuan's publish/read-only boundary can be broken through /api/av/removeUnusedAttributeView.

A publish-service Reader context can call this endpoint because it is protected only by CheckAuth, and publish requests are forwarded upstream with a valid RoleReader JWT. The handler accepts attacker-controlled id input and passes it directly into a filesystem delete sink:

  • no admin check
  • no readonly check
  • no node-ID validation
  • no subpath enforcement
  • no verification that the target AV is actually unused

Because the sink builds the file path with:

filepath.Join(util.DataDir, "storage", "av", id+".json")

an attacker can supply ../ path traversal sequences and delete arbitrary .json files reachable from the workspace, rather than only data/storage/av/<id>.json.

This is a real write/destructive authorization bug, not a visible=false listing issue.

Impact

An attacker with publish-reader access can delete arbitrary .json files under the workspace path reachable from data/storage/av/ using traversal payloads.

Examples of realistic targets:

  • ../local -> data/storage/local.json
  • ../../storage/recent-doc -> data/storage/recent-doc.json
  • ../../storage/outline -> data/storage/outline.json
  • ../../storage/criteria -> data/storage/criteria.json
  • ../../../conf/conf -> conf/conf.json

Practical impact includes:

  • persistent destruction of Attribute View definitions
  • deletion of global workspace configuration
  • deletion of local storage and outline state
  • corruption of user state that survives reload
  • forced reset/recovery flows or broken UI behavior

Even if some files can be regenerated, this is still a publish-reader to persistent server-side delete primitive against workspace data.

Source to Sink

Source 1: publish service issues Reader-role JWTs

kernel/server/proxy/publish.go

The publish reverse proxy forwards requests to the kernel and injects X-Auth-Token.

Source 2: publish accounts are Reader-role accounts

kernel/model/auth.go

Publish accounts are created with RoleReader.

Source 3: generic auth accepts Reader role

kernel/model/session.go

CheckAuth allows:

  • RoleAdministrator
  • RoleEditor
  • RoleReader

Source 4: dangerous route is only guarded by CheckAuth

kernel/api/router.go#L507

ginServer.Handle("POST", "/api/av/removeUnusedAttributeView", model.CheckAuth, removeUnusedAttributeView)

Source 5: attacker-controlled id is passed straight to model

kernel/api/av.go#L32-L45

avID := arg["id"].(string)
model.RemoveUnusedAttributeView(avID)

There is no:

  • util.InvalidIDPattern(...)
  • allowlist
  • normalization check
  • verification that the caller should modify this object

Sink: path traversal reaches filesystem delete

kernel/model/attribute_view.go#L49-L76

absPath := filepath.Join(util.DataDir, "storage", "av", id+".json")
...
if err = filelock.RemoveWithoutFatal(absPath); err != nil {

Because id is not validated, inputs like ../../../conf/conf resolve outside data/storage/av/.

Examples:

../../../conf/conf              -> <workspace>/conf/conf.json
../local                        -> <workspace>/data/storage/local.json
../../storage/recent-doc        -> <workspace>/data/storage/recent-doc.json
../../storage/outline           -> <workspace>/data/storage/outline.json
../../storage/criteria          -> <workspace>/data/storage/criteria.json

Proof of Concept

Prerequisite:

  • publish service enabled
  • attacker has publish-reader access, or anonymous publish access if publish auth is disabled

Request:

POST /api/av/removeUnusedAttributeView HTTP/1.1
Host: <publish-host>:6808
Content-Type: application/json

{
  "id": "../../../conf/conf"
}

Expected result:

  • request is accepted from publish context
  • backend resolves target to <workspace>/conf/conf.json
  • target file is removed

Safer validation targets for testing:

{"id":"../local"}
{"id":"../../storage/recent-doc"}
{"id":"../../storage/outline"}

These demonstrate traversal without immediately targeting the main config file.

Root Cause

Two separate authorization/design failures combine here:

  1. publish Reader contexts are treated as fully authenticated for CheckAuth routes
  2. a destructive internal endpoint trusts raw id input as a filesystem path component

Either issue would be dangerous; together they produce a high-impact delete primitive.

Remediation

1. Lock the endpoint down properly

/api/av/removeUnusedAttributeView should require:

  • CheckAdminRole
  • CheckReadonly

2. Validate the identifier

Reject anything that is not a valid AV/node ID. For example, enforce util.InvalidIDPattern(...) or an equivalent strict allowlist.

3. Enforce subpath safety

After path construction, verify the resolved path remains under the intended directory:

base := filepath.Join(util.DataDir, "storage", "av")
absPath := filepath.Join(base, id+".json")
if !util.IsSubPath(base, absPath) {
    return error
}

4. Actually verify "unused"

Before deletion, confirm that the requested AV is in the computed unused set. The current single-delete API does not do that at all.

5. Add regression tests

Add negative tests proving a publish-reader context cannot:

  • call this endpoint successfully
  • delete a valid AV directly
  • pass traversal strings such as ../local
  • escape data/storage/av/

Key References

References

@88250 88250 published to siyuan-note/siyuan Apr 10, 2026
Published to the GitHub Advisory Database Apr 10, 2026
Reviewed Apr 10, 2026
Last updated Apr 10, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
None
Integrity
Low
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:H

EPSS score

Weaknesses

Path Traversal: '../filedir'

The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize ../ sequences that can resolve to a location that is outside of that directory. Learn more on MITRE.

CVE ID

No known CVE

GHSA ID

GHSA-vw86-c94w-v3x4

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.