AI and data stack advisories

Severe, 6 weeks1496Projects326

1496 severe, 6 weeks · 326 projects

Open WebUIGHSA-jx2x-j75f-xq3j

Open WebUI: improper authorization

Open WebUI

CVE-2026-45316 · Published May 14, 2026 · updated Jul 13, 2026

Low3.5
Fix: upgrade to 0.9.3 or later
GitHub advisory

Summary

The POST /api/v1/notes/{id}/pin endpoint performs a write operation (toggling the is_pinned field) but only checks for read permission. Users with read-only access to a shared note can pin/unpin it, which is a state-modifying action that should require write permission. All other write endpoints (update, delete, access/update) correctly check for write permission.

Details

**Affected code: backend/open_webui/routers/notes.py lines 412-444**

```python

@router.post('/{id}/pin', response_model=Optional[NoteModel])

async def pin_note_by_id(...):

# ...

if user.role != 'admin' and (

user.id != note.user_id

and not await AccessGrants.has_access(

user_id=user.id,

resource_type='note',

resource_id=note.id,

permission='read', # BUG: should be 'write'

db=db,

)

):

raise HTTPException(...)

note = await Notes.toggle_note_pinned_by_id(id, db=db) # write operation

`

Compare with update endpoint (correct, line 318-327):

```python

async def update_note_by_id(...):

# ...

and not await AccessGrants.has_access(

permission='write', # correctly checks 'write'

)

`

PoC

Environment: Open WebUI v0.9.2, default configuration with notes sharing enabled.

Setup:

1. UserA creates a note

2. UserA shares note with...

Affected versions

PackageAffectedFixed in
open-webui
PyPI
< 0.9.30.9.3
Details and references

### Summary The `POST /api/v1/notes/{id}/pin` endpoint performs a write operation (toggling the `is_pinned` field) but only checks for `read` permission. Users with read-only access to a shared note can pin/unpin it, which is a state-modifying action that should require `write` permission. All other write endpoints (update, delete, access/update) correctly check for `write` permission. ### Details **Affected code: `backend/open_webui/routers/notes.py` lines 412-444** ```python @router.post('/{id}/pin', response_model=Optional[NoteModel]) async def pin_note_by_id(...): # ... if user.role != 'admin' and ( user.id != note.user_id and not await AccessGrants.has_access( user_id=user.id, resource_type='note', resource_id=note.id, permission='read', # BUG: should be 'write' db=db, ) ): raise HTTPException(...) note = await Notes.toggle_note_pinned_by_id(id, db=db) # write operation ``` **Compare with update endpoint (correct, line 318-327):** ```python async def update_note_by_id(...): # ... and not await AccessGrants.has_access( permission='write', # correctly checks 'write' ) ``` ### PoC **Environment:** Open WebUI v0.9.2, default configuration with notes sharing enabled. **Setup:** 1. UserA creates a note 2. UserA shares note with UserB with `read` permission via `POST /api/v1/notes/{id}/access/update` with `{"access_grants":[{"principal_type":"user","principal_id":"USERB_ID","permission":"read"}]}` **Test:** ```bash # Step 1: UserB reads note (READ permission) -> 200 OK, write_access: false curl -s http://TARGET/api/v1/notes/$NOTE_ID \ -H "Authorization: Bearer $TOKEN_B" # Result: 200 OK, "write_access": false # Step 2: UserB updates note (WRITE operation) -> 403 Forbidden (correctly blocked) curl -s -X POST http://TARGET/api/v1/notes/$NOTE_ID/update \ -H "Authorization: Bearer $TOKEN_B" \ -H "Content-Type: application/json" \ -d '{"title":"HACKED","content":"pwned","data":{"type":"note"}}' # Result: 403 Forbidden # Step 3: UserB pins note (WRITE operation, but only checks READ) -> 200 OK (BUG!) curl -s -X POST http://TARGET/api/v1/notes/$NOTE_ID/pin \ -H "Authorization: Bearer $TOKEN_B" # Result: 200 OK, "is_pinned": true # Step 4: UserB can toggle pin repeatedly curl -s -X POST http://TARGET/api/v1/notes/$NOTE_ID/pin \ -H "Authorization: Bearer $TOKEN_B" # Result: 200 OK, "is_pinned": false (toggled back) ``` **E2E Verified Result:** - Step 1: UserB reads note (READ) -> 200 OK ✓ - Step 2: UserB updates note (WRITE) -> 403 Forbidden ✓ (correctly blocked) - Step 3: UserB pins note (WRITE via READ) -> 200 OK, is_pinned: true ✗ (BUG) - Step 4: UserB toggles pin again -> 200 OK, is_pinned: false ✗ (repeated write) ### Impact - A user with only `read` access to a shared note can toggle its `is_pinned` status - This modifies the note's state without write authorization - The pin status change is visible to the note owner and all other users with access - Privilege escalation from read to write on the pin operation **Limitations:** Only affects the `is_pinned` boolean field. Cannot modify title, content, or access_grants. Requires at least read access via explicit sharing. ### Fix One-line fix , change `permission='read'` to `permission='write'` in `pin_note_by_id`: ```python # backend/open_webui/routers/notes.py, line 437 - permission='read', + permission='write', ``` This makes the pin endpoint consistent with update and delete endpoints.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Severity from
GitHub (reviewed advisory)
Weakness
CWE-863
Also known as
CVE-2026-45316, PYSEC-2026-2745

More Open WebUI advisories

All Open WebUI