Summary
On chromadb 1.5.9, chromadb.AsyncHttpClient(..., ssl=True) does not verify the server
certificate. The sync HttpClient, with the same settings, does.
Settings.chroma_server_ssl_verify defaults to None (chromadb/config.py:146). The two clients
read that default differently.
Sync, chromadb/api/fastapi.py:90 on main (:85 in the 1.5.9 wheel):
if self._settings.chroma_server_ssl_verify is not None:
self._session = httpx.Client(timeout=None, limits=self.http_limits,
verify=self._settings.chroma_server_ssl_verify)
else:
self._session = httpx.Client(timeout=None, limits=self.http_limits)
Async, chromadb/api/async_fastapi.py:144 on main (:139 in the wheel):
self._clients[loop_hash] = httpx.AsyncClient(
timeout=None,
headers=headers,
verify=self._settings.chroma_server_ssl_verify or False,
limits=self.http_limits,
)
None or False is False, and verify=False in httpx turns off chain and hostname checking. So
the async client accepts any certificate that is offered, with no error and no warning.
Reproduction
chromadb 1.5.9, Python 3.14.0, fresh venv. The listener is a plain TLS socket holding a
self-signed cert for CN=localhost, addressed as 127.0.0.1 so the name does not match either. It
counts completed handshakes. Nothing leaves the machine.
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3 -nodes -subj "/CN=localhost"
await chromadb.AsyncHttpClient(host="127.0.0.1", port=P, ssl=True) # A
chromadb.HttpClient(host="127.0.0.1", port=P, ssl=True) # B
await chromadb.AsyncHttpClient(host="127.0.0.1", port=P, ssl=True,
settings=Settings(chroma_server_ssl_verify=True)) # C
| case |
TLS handshakes completed |
outcome |
| A, async, default settings |
1 |
handshake succeeded against an untrusted cert |
| B, sync, default settings |
0 |
SSLCertVerificationError |
| C, async, ssl_verify=True |
0 |
SSLCertVerificationError |
Read the handshake column. Row A then fails on the fake origin's response shape, which is not part
of the claim. Row B is what proves the certificate really is untrusted. Row C is what proves the
setting still works when it is truthy, so this is about the default and not about verification
being broken everywhere.
When it changed
The commit history on that file puts it at commit 22e7f84, PR #3236 "[BUG] Add headers to async client
requests", 2024-12-03. The whole diff is:
- self._clients[loop_hash] = httpx.AsyncClient(timeout=None)
+ self._clients[loop_hash] = httpx.AsyncClient(
+ timeout=None,
+ headers=self._settings.chroma_server_headers,
+ verify=self._settings.chroma_server_ssl_verify or False,
+ )
Before that the async client had no verify argument at all, so it used the httpx default and
verified. I checked five earlier commits on the file and none of them mention ssl_verify. The PR
was about headers, so the or False looks incidental rather than intended.
Suggested fix
Match the sync branch:
kwargs = {"timeout": None, "headers": headers, "limits": self.http_limits}
if self._settings.chroma_server_ssl_verify is not None:
kwargs["verify"] = self._settings.chroma_server_ssl_verify
self._clients[loop_hash] = httpx.AsyncClient(**kwargs)
That keeps chroma_server_ssl_verify=False working for people who want to skip verification, and
keeps a CA bundle path working, while restoring the default.
Happy to open a PR with a test that asserts the async client refuses a self-signed cert by default,
if that is useful.
Summary
On chromadb 1.5.9,
chromadb.AsyncHttpClient(..., ssl=True)does not verify the servercertificate. The sync
HttpClient, with the same settings, does.Settings.chroma_server_ssl_verifydefaults toNone(chromadb/config.py:146). The two clientsread that default differently.
Sync,
chromadb/api/fastapi.py:90on main (:85in the 1.5.9 wheel):Async,
chromadb/api/async_fastapi.py:144on main (:139in the wheel):None or FalseisFalse, andverify=Falsein httpx turns off chain and hostname checking. Sothe async client accepts any certificate that is offered, with no error and no warning.
Reproduction
chromadb 1.5.9, Python 3.14.0, fresh venv. The listener is a plain TLS socket holding a
self-signed cert for CN=localhost, addressed as 127.0.0.1 so the name does not match either. It
counts completed handshakes. Nothing leaves the machine.
Read the handshake column. Row A then fails on the fake origin's response shape, which is not part
of the claim. Row B is what proves the certificate really is untrusted. Row C is what proves the
setting still works when it is truthy, so this is about the default and not about verification
being broken everywhere.
When it changed
The commit history on that file puts it at commit 22e7f84, PR #3236 "[BUG] Add headers to async client
requests", 2024-12-03. The whole diff is:
Before that the async client had no
verifyargument at all, so it used the httpx default andverified. I checked five earlier commits on the file and none of them mention
ssl_verify. The PRwas about headers, so the
or Falselooks incidental rather than intended.Suggested fix
Match the sync branch:
That keeps
chroma_server_ssl_verify=Falseworking for people who want to skip verification, andkeeps a CA bundle path working, while restoring the default.
Happy to open a PR with a test that asserts the async client refuses a self-signed cert by default,
if that is useful.