middleware/agentRuntimeAuth.ts:62 and services/agentWebSocketService.ts:308 both resolve an agent's identity with
User.findOne({ 'agentRuntimeTokens.tokenHash': tokenHash, ... })
and there is no index on that path. backend/models/User.ts declares exactly two userSchema.index(...) calls (:365 authProviders, :366 the compound unique) plus field-level index: true on two entitlements.* keys. Nothing covers agentRuntimeTokens.tokenHash.
That query is on the hot path: it runs on every /api/agents/runtime/* request and on every agent WebSocket connect, so each one is a collection scan of User. lastUsedAt is then written back through the same unindexed predicate (:78, and agentWebSocketService.ts:322), so it is two scans per authenticated agent call, not one.
Fix is one line in the schema:
userSchema.index({ 'agentRuntimeTokens.tokenHash': 1 }, { sparse: true });
sparse matters here — most User rows carry agentRuntimeTokens: [] and have no value at that path, so a sparse multikey index only stores entries for rows that actually hold a token.
Scale caveat, stated rather than left to be inferred: at the current user count a scan is not a visible latency problem, and I have not measured one. This is filed as a correctness-of-shape issue on a path whose cost grows with total users while its working set grows with agents only.
One thing I am explicitly NOT asking for
I previously suggested this array should also get select: false, by symmetry with apiToken. That reasoning was wrong and I am withdrawing it:
apiToken earns select: false (models/User.ts:234) because it is a plaintext cm_ bearer — an incidental findById() leaks a live credential.
agentRuntimeTokens[] stores only tokenHash. agentRuntimeAuth.ts:67 matches on the hash; the plaintext is never persisted.
- The array is already stripped from responses by
controllers/userController.ts:16 (SECRET_USER_FIELDS), with a regression test at __tests__/unit/controllers/userController.secretLeak.test.js:95.
select: false would still be defence in depth against a future route that returns a user without going through that controller, but the precedent I cited for it does not apply, and adding it means auditing every .select('agentRuntimeTokens...') call site (there are ~10) for a now-required +. Not obviously worth it; someone else's call.
Raised in the sprint pod at message 59725; filed here so it is durable somewhere other than pod prose.
middleware/agentRuntimeAuth.ts:62andservices/agentWebSocketService.ts:308both resolve an agent's identity withand there is no index on that path.
backend/models/User.tsdeclares exactly twouserSchema.index(...)calls (:365authProviders,:366the compound unique) plus field-levelindex: trueon twoentitlements.*keys. Nothing coversagentRuntimeTokens.tokenHash.That query is on the hot path: it runs on every
/api/agents/runtime/*request and on every agent WebSocket connect, so each one is a collection scan ofUser.lastUsedAtis then written back through the same unindexed predicate (:78, andagentWebSocketService.ts:322), so it is two scans per authenticated agent call, not one.Fix is one line in the schema:
sparsematters here — mostUserrows carryagentRuntimeTokens: []and have no value at that path, so a sparse multikey index only stores entries for rows that actually hold a token.Scale caveat, stated rather than left to be inferred: at the current user count a scan is not a visible latency problem, and I have not measured one. This is filed as a correctness-of-shape issue on a path whose cost grows with total users while its working set grows with agents only.
One thing I am explicitly NOT asking for
I previously suggested this array should also get
select: false, by symmetry withapiToken. That reasoning was wrong and I am withdrawing it:apiTokenearnsselect: false(models/User.ts:234) because it is a plaintextcm_bearer — an incidentalfindById()leaks a live credential.agentRuntimeTokens[]stores onlytokenHash.agentRuntimeAuth.ts:67matches on the hash; the plaintext is never persisted.controllers/userController.ts:16(SECRET_USER_FIELDS), with a regression test at__tests__/unit/controllers/userController.secretLeak.test.js:95.select: falsewould still be defence in depth against a future route that returns a user without going through that controller, but the precedent I cited for it does not apply, and adding it means auditing every.select('agentRuntimeTokens...')call site (there are ~10) for a now-required+. Not obviously worth it; someone else's call.Raised in the sprint pod at message 59725; filed here so it is durable somewhere other than pod prose.