# SiYuan: Publish Reader Can Arbitrarily Delete Attribute View Files via `/api/av/removeUnusedAttributeView`

Publish reader JWT (RoleReader) passes CheckAuth on /api/av/removeUnusedAttributeView, letting low-priv users delete arbitrary attribute view JSON files by id.

- Payout: Research
- Severity: high
- Program: SiYuan
- Found by: ch1nhpd
- Disclosed: 2026-04-10
- CVE: CVE-2026-40259
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-7m5h-w69j-qggg
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-7m5h-w69j-qggg
- Tags: area:web domain:web impact:dos impact:integrity kind:research platform:ghsa sev:high tech:go technique:source-review vuln:access-control vuln:auth-bypass vuln:idor

**Target:** /api/av/removeUnusedAttributeView endpoint on publish service
**Root cause:** Route protected only by generic CheckAuth which accepts RoleReader; handler forwards caller-controlled id to RemoveUnusedAttributeView, which deletes data/storage/av/<id>.json without checking write permission or that the AV is actually unused.
**Steps:**
1. Browse published content containing an attribute view and extract `data-av-id` from the DOM.
2. Send `POST /api/av/removeUnusedAttributeView` to the publish host with `Content-Type: application/json`, `Authorization: Basic <publish-account-creds-if-enabled>`, body `{"id": "<exposed-data-av-id>"}`.
3. Publish proxy injects RoleReader token; CheckAuth accepts; handler passes id to model.RemoveUnusedAttributeView.
4. Backend deletes `data/storage/av/<avID>.json` and increments sync state.
**How found:** Code review of the router: noticed /api/av/removeUnusedAttributeView registered with only model.CheckAuth (no CheckAdminRole/CheckReadonly), then traced the handler into RemoveUnusedAttributeView and saw it deletes by id without verifying the AV is unused.
**Exploitation:** Attacker with publish-reader access (or anonymous if publish auth disabled) reads a valid avID from published page markup, then POSTs it to the publish endpoint; the proxy's injected RoleReader token satisfies CheckAuth and the file is deleted, breaking published and local AV rendering until restored.
**Insight:** Cleanup-style endpoints named 'removeUnused*' often skip the 'is it actually unused' check and are registered with generic auth; grep router registrations for destructive handlers lacking CheckAdminRole/CheckReadonly and confirm the model re-validates preconditions.

## Proof of concept (copied exactly from the original report)

**POST request deleting arbitrary AV by id**

```
POST /api/av/removeUnusedAttributeView HTTP/1.1
Host: <publish-host>:<publish-port>
Content-Type: application/json
Authorization: Basic <publish-account-creds-if-enabled>

{
  "id": "<exposed-data-av-id>"
}
```

