Bug
In the new JS client (clients/new-js/packages/chromadb/src/chroma-client.ts), the _path() method checks !this._tenant || !this._database before calling await getUserIdentity() (around line 191). However, concurrent callers that all enter _path() before any of them has completed getUserIdentity() will all see the uninitialized state, all fire getUserIdentity(), and the last writer wins on this._tenant/this._database.
More critically: a caller that enters after tenant/database are set but while getUserIdentity() is still in flight for another caller sees the values as initialized (check passes), proceeds to build a path using those values — but a concurrent getUserIdentity() call may still overwrite them partway through.
This is the same class of TOCTOU race fixed in #7487 (old Python client) and #7494 (old JS client), now present in the new JS client.
Reproduction
const client = new ChromaDBClient({ /* cloud config */ });
// Two concurrent operations before identity is resolved:
await Promise.all([
client.listCollections(),
client.createCollection({ name: "test" }),
]);
// Both race on _tenant/_database initialization
Fix
Store the in-flight getUserIdentity() promise in a class field the first time it's called, and return that same promise to all concurrent callers:
private _identityPromise: Promise<void> | null = null;
private async _ensureIdentity(): Promise<void> {
if (this._tenant && this._database) return;
if (!this._identityPromise) {
this._identityPromise = getUserIdentity().then(id => {
this._tenant = id.tenant;
this._database = id.database;
});
}
return this._identityPromise;
}
Then call await this._ensureIdentity() at the start of _path().
Bug
In the new JS client (
clients/new-js/packages/chromadb/src/chroma-client.ts), the_path()method checks!this._tenant || !this._databasebefore callingawait getUserIdentity()(around line 191). However, concurrent callers that all enter_path()before any of them has completedgetUserIdentity()will all see the uninitialized state, all firegetUserIdentity(), and the last writer wins onthis._tenant/this._database.More critically: a caller that enters after tenant/database are set but while
getUserIdentity()is still in flight for another caller sees the values as initialized (check passes), proceeds to build a path using those values — but a concurrentgetUserIdentity()call may still overwrite them partway through.This is the same class of TOCTOU race fixed in
#7487(old Python client) and#7494(old JS client), now present in the new JS client.Reproduction
Fix
Store the in-flight
getUserIdentity()promise in a class field the first time it's called, and return that same promise to all concurrent callers:Then call
await this._ensureIdentity()at the start of_path().