This is a receptionist who runs whatever package you hand over before checking whether you belong in the building
CVE-2026-45829 is a pre-auth remote code execution flaw in the ChromaDB Python FastAPI server. On chromadb versions >= 1.0.0 and <= 1.5.9, the create_collection flow builds an attacker-controlled embedding function before it performs the auth check. If the attacker sets kwargs.trust_remote_code=true and points model_name at a malicious Hugging Face-style repository, Chroma fetches and executes that code inside the server process. The Rust deployment path is not affected.
In a vacuum this looks like a straight CRITICAL: network-reachable, no auth, no user interaction, code exec. In real estates, the severity comes down one notch because the blast population is narrower than the raw bug suggests: you need the Python backend, not the Rust one; you need the API to be reachable from the attacker; and many enterprises do not expose their vector stores broadly. But if you *do* run exposed Python Chroma, the vendor-less score inflation debate is academic — it is still a serious server takeover path.
4 steps from start to impact.
Reach a Python Chroma API
POST /api/v2/tenants/{tenant}/databases/{db}/collections, which is documented as authenticated but processes the request body before the auth gate fires. Weaponized tool: a simple curl request or any HTTP client; no exploit kit required.- Target is running the Python FastAPI server, not the Rust path
- Attacker can reach the Chroma API port over network
- Affected package version is in the vulnerable range
- Many deployments are internal-only and never exposed to the internet
- Some self-hosted environments front Chroma with an API gateway, VPN, or private service mesh
- Organizations using the Rust deployment path are out of scope
/api/v2/heartbeat. Hadrian published a conservative nuclei fingerprint for the vulnerable behavior.Send a malicious embedding configuration
kwargs.trust_remote_code=true. Chroma passes those attacker-controlled settings into embedding-function builders, including SentenceTransformerEmbeddingFunction.build_from_config, which forwards kwargs into the model loader. Weaponized tool: a Hugging Face-style repo containing attacker Python modules.- Embedding-function config from the request is accepted
- The server environment has the relevant model-loading dependencies available
- Outbound access or local path access exists to retrieve the malicious model payload
- If the runtime cannot resolve or load the attacker model, exploitation can fail
- Some hardened hosts restrict outbound internet egress to model registries
- Not every Chroma deployment includes the same optional ML dependencies
trust_remote_code semantics.Exploit the auth-ordering flaw
load_create_collection_configuration_from_json() runs before sync_auth_request(). That means model code can execute *before* the request is rejected. HiddenLayer showed the request can return an error outwardly while the payload already ran inside the Chroma process. Weaponized tool: the transformers loading path behind AutoModel.from_pretrained() or equivalent upstream loaders.- The vulnerable code path is intact in the installed package
- The chosen embedding function forwards attacker kwargs into model loading
- No upstream wrapper blocks the dangerous fields before Chroma sees them
- A reverse proxy that enforces auth *before* the request reaches Chroma can break the chain
- Custom request validation or schema stripping at the edge may remove the exploit fields
- Some defensive egress controls stop the remote model fetch even though the app logic is vulnerable
401/403/500 after code already executed. EDR on the host is your best shot at seeing the spawned process, file write, or suspicious module import.Take over the server process
- The service account has access to valuable local secrets or adjacent services
- EDR or container confinement does not immediately stop post-exploitation behavior
- Least-privilege containers and locked-down service accounts can reduce lateral movement
- Read-only filesystems, seccomp, and egress controls can cap the blast radius
- Some AI stacks isolate vector stores from broader crown-jewel networks
/api/v2/tenants/.../collections.- Turning on Chroma's own auth alone does not solve this, because the vulnerable Python code path performs dangerous model loading before the auth check.
- A generic WAF is weak here unless it specifically understands and blocks the dangerous JSON fields; the exploit rides a normal API route with application-valid structure.
- MFA is irrelevant because the exploit does not need an account.
- Relying on HTTP status codes is dangerous; the request can fail outwardly while attacker code already executed.
The supporting signals.
| In-the-wild status | As of 2026-05-31, I found no CISA KEV entry and no authoritative public reporting of active exploitation campaigns. Treat it as *highly weaponizable*, not yet *proven at scale*. |
|---|---|
| Proof-of-concept availability | Yes. HiddenLayer published a working demonstration, the public GitHub issue documents the vulnerable call chain, and Hadrian published a nuclei fingerprint for exposure validation. |
| EPSS | 0.168% (38th percentile) in the GitHub Advisory view sourced from FIRST. That is low threat-frequency telemetry, not low impact. |
| KEV status | Not listed in the CISA KEV catalog as of 2026-05-31. |
| Third-party CVSS context | There is no vendor-maintainer severity baseline, which is why this is ASSESSED AT HIGH rather than upgraded or downgraded. Public third-party scoring exists: GitHub Reviewed shows 9.3 CVSS v4 and NVD displays a HiddenLayer CNA vector, but neither is a maintainer patch-priority statement. |
| CVSS vector interpretation | GitHub Reviewed lists CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:P, which matches the technical reality: network reachable, no auth, no user action, full server compromise. |
| Affected versions | The strongest public package metadata says chromadb >= 1.0.0 and <= 1.5.9 on the Python project path. HiddenLayer originally stated unpatched through 1.5.8; GitHub Advisory was later updated to include 1.5.9. |
| Fixed versions | None published in the GitHub Advisory as of 2026-05-31. No distro backport guidance or vendor-confirmed fixed Python release was found. The Rust deployment path is not affected, which matters operationally. |
| Scanning and exposure data | HiddenLayer reports 73% of internet-exposed ChromaDB instances discovered via Shodan were in the vulnerable version range. Hadrian's write-up shows a practical nuclei probe for confirming exposure without executing code. |
| Disclosure and researcher | Publicly disclosed 2026-05-18 by HiddenLayer; research article attributed to Esteban Tonglet. HiddenLayer says disclosure attempts started on 2026-02-17. |
Sources.
Why this verdict
- Network-reachable pre-auth RCE: no credentials, no user interaction, and code runs before auth enforcement on the vulnerable Python path.
- Population narrowed by deployment choice: the Rust deployment path is not affected, so this is not a whole-product emergency across every Chroma install.
- Exposure is the make-or-break prerequisite: requiring HTTP reachability to the API means private-only or tightly segmented deployments are materially safer than internet-exposed ones.
- No vendor-maintainer fixed release as of 2026-05-31: lack of an official patch increases operational risk because mitigation has to carry the load.
- Low EPSS and no KEV: current exploitation telemetry is weak, which argues against a top-bucket CRITICAL fleet-wide panic score.
Why not higher?
I am not calling this CRITICAL fleet-wide because the real-world reachable population is smaller than the naked bug suggests. You need the vulnerable Python server path, not Rust, and you need attacker reachability to the API; those are meaningful friction points when you are triaging across 10,000 hosts rather than staring at a single exposed AI service.
Why not lower?
I am not dropping this to MEDIUM because once the prerequisites are met, the exploit chain is brutally short and the impact is full server compromise. Pre-auth code execution against an exposed service is still the kind of bug that becomes a breach, not a nuisance.
Crowdsourced verification payload.
Run this on the target host, container, or image where chromadb is installed. Invoke it with python3 verify_cve_2026_45829.py using the same Python environment that runs Chroma; no admin rights are required, but you do need filesystem access to the installed package. The script performs a static package inspection and prints exactly one of VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env python3
# verify_cve_2026_45829.py
# Static verifier for CVE-2026-45829 in the ChromaDB Python package.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import importlib
import importlib.util
import os
import re
import sys
from pathlib import Path
VULN_MIN = (1, 0, 0)
VULN_MAX = (1, 5, 9)
def parse_version(v):
if not v:
return None
nums = re.findall(r"\d+", str(v))
if not nums:
return None
parts = [int(x) for x in nums[:3]]
while len(parts) < 3:
parts.append(0)
return tuple(parts)
def read_text(p):
try:
return Path(p).read_text(encoding="utf-8", errors="ignore")
except Exception:
return ""
def main():
try:
chromadb = importlib.import_module("chromadb")
except Exception:
print("UNKNOWN")
sys.exit(2)
version = getattr(chromadb, "__version__", None)
vtuple = parse_version(version)
pkg_dir = Path(getattr(chromadb, "__file__", "")).resolve().parent
if not pkg_dir.exists():
print("UNKNOWN")
sys.exit(2)
fastapi_file = pkg_dir / "server" / "fastapi" / "__init__.py"
collcfg_file = pkg_dir / "api" / "collection_configuration.py"
sentxf_file = pkg_dir / "utils" / "embedding_functions" / "sentence_transformer_embedding_function.py"
fastapi_txt = read_text(fastapi_file)
collcfg_txt = read_text(collcfg_file)
sentxf_txt = read_text(sentxf_file)
# Heuristic 1: vulnerable ordering in create_collection path.
idx_load = fastapi_txt.find("load_create_collection_configuration_from_json")
idx_auth = fastapi_txt.find("sync_auth_request")
order_vuln = idx_load != -1 and idx_auth != -1 and idx_load < idx_auth
# Heuristic 2: attacker-controlled embedding config reaches known embedding functions.
cfg_vuln = (
'known_embedding_functions[ef_config["name"]]' in collcfg_txt and
'build_from_config(ef_config["config"])' in collcfg_txt
)
# Heuristic 3: sentence transformer forwards kwargs to constructor, enabling trust_remote_code flow.
kwargs_forwarded = (
'kwargs = config.get("kwargs", {})' in sentxf_txt and
'**kwargs' in sentxf_txt
)
# Heuristic 4: evidence of an obvious patch pattern.
auth_before_load = idx_auth != -1 and idx_load != -1 and idx_auth < idx_load
strips_kwargs = (
'pop("kwargs"' in fastapi_txt or
'del json_map["embedding_function"]["config"]["kwargs"]' in collcfg_txt or
'trust_remote_code' in collcfg_txt and 'False' in collcfg_txt
)
in_version_range = vtuple is not None and VULN_MIN <= vtuple <= VULN_MAX
if order_vuln and cfg_vuln and kwargs_forwarded and (in_version_range or version is None):
print("VULNERABLE")
sys.exit(1)
if auth_before_load or strips_kwargs:
print("PATCHED")
sys.exit(0)
# Version-only fallback: GitHub Advisory says <= 1.5.9 affected and patched versions are None.
if in_version_range:
print("VULNERABLE")
sys.exit(1)
print("UNKNOWN")
sys.exit(2)
if __name__ == "__main__":
main()