#16025·LibreChat

[Bug]: checkBan throws "key.startsWith is not a function" and answers 500 on /oauth when USE_REDIS is off

Author: wichtkingCreated Sep 16, 2026Updated Sep 16, 2026

What happened?

Every social (Google) sign-in throws a TypeError inside checkBan and the request under /oauth is answered with 500 instead of the normal response. The login itself still completes — the user document and a refresh-token session are created a few milliseconds before the error — so the visible damage is a 500 on the OAuth path plus a permanent error line on every login.

Root cause: checkBan hands Keyv a non-string key when USE_REDIS is off.

api/server/middleware/checkBan.js:

javascript
const banCache = new Keyv({ store: keyvMongo, namespace: ViolationTypes.BAN, ttl: 0 });

const getBanCacheKey = (prefix, value, useRedis) => {
  if (!value) return '';
  return useRedis ? `ban_cache:${prefix}:${value}` : value;   // <- raw value, not a string
};

req.ip = removePorts(req);
let userId = req.user?.id ?? req.user?._id ?? null;            // <- ObjectId on the OAuth path
const userKey = getBanCacheKey('user', userId, useRedis);
const [cachedIPBan, cachedUserBan] = await Promise.all([
  ipKey ? banCache.get(ipKey) : undefined,
  userKey ? banCache.get(userKey) : undefined,                 // <- throws here (line 108)
]);

With USE_REDIS unset, useRedis is false, so userKey is the raw userId. On the OAuth path req.user carries an ObjectId rather than a string, and keyv 5.6.0's _getKeyPrefix does key.startsWith(...):

javascript
_getKeyPrefix(key) {
  if (!this._useKeyPrefix) { return key; }
  if (!this._namespace) { return key; }
  if (key.startsWith(`${this._namespace}:`)) { return key; }   // TypeError for an ObjectId
  return `${this._namespace}:${key}`;
}

ViolationTypes.BAN resolves to the string "ban", so _namespace is fine — the offending value is the key.

Reproduced in isolation inside the released image (not an inference — run against the real keyvMongo store and the real ViolationTypes):

javascript
const { Keyv } = require('keyv');
const { keyvMongo } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
const { Types } = require('mongoose');
const banCache = new Keyv({ store: keyvMongo, namespace: ViolationTypes.BAN, ttl: 0 });
// get('some-string')            -> OK
// get(new Types.ObjectId())     -> TypeError: key.startsWith is not a function
// get(12345)                    -> TypeError: key.startsWith is not a function
// getBanCacheKey('user', ObjectId, false) -> typeof "object"   (USE_REDIS=false)
// getBanCacheKey('user', ObjectId, true)  -> typeof "string"   (USE_REDIS=true does not fail)

Suggested fix: make the key a string before it reaches Keyv, e.g. getBanCacheKey('user', userId != null ? String(userId) : '', useRedis), or drop the useRedis ? ... : value branch and always build a prefixed string, since the raw-value branch is what breaks. (Note the same function is also used for req.ip; that one is a string today, so it does not fail — the object case is the user key.)

Version Information

LibreChat: v0.8.8-rc2
Image: ghcr.io/danny-avila/librechat:v0.8.8-rc2@sha256:9e5266c6b83fa68f69f35ef3a81b4388c20ee3a83a942c428de53a221e9c8583
keyv: 5.6.0 (from node_modules inside the image)
Node.js: v24.16.0
Cache/store: keyv-mongo, Redis NOT used (USE_REDIS unset)
BAN_VIOLATIONS=true  (default)
ALLOW_SOCIAL_LOGIN=true, ALLOW_SOCIAL_REGISTRATION=true (Google)
Provider: custom OpenAI-compatible endpoint (unrelated to this bug)
Deployment: docker compose, MongoDB 8.0

Checked main before filing: api/server/middleware/checkBan.js is unchanged in every line quoted above (last commit touching the file is 146664e0), so this is not fixed on main today.

Steps to Reproduce

  1. Run LibreChat with BAN_VIOLATIONS=true (default) and no Redis (USE_REDIS unset).
  2. Enable a social login (Google) and sign in with it.
  3. docker logs <librechat-container> → the TypeError fires 7–9 ms after the user document is created, with "request_path":"/oauth" and status 500.

The same failure can be triggered deterministically with the 12-line script above (it needs no OAuth flow at all).

Relevant log output

{"level":"error","message":"key.startsWith is not a function","name":"TypeError",
 "requestId":"5ab20394-2831-4e25-b7b0-40de993bf3c0","request_method":"GET","request_path":"/oauth",
 "stack":"TypeError: key.startsWith is not a function
    at Keyv._getKeyPrefix (/app/node_modules/keyv/dist/index.cjs:499:13)
    at Keyv.get (/app/node_modules/keyv/dist/index.cjs:527:71)
    at checkBan (/app/api/server/middleware/checkBan.js:108:26)
    at /app...","timestamp":"2026-09-16T15:58:15.356Z"}

Correlation with user creation (same deployment, two separate accounts):

15:58:15.347Z  user created (provider: google)
15:58:15.356Z  TypeError on /oauth   (+9 ms)
16:20:16.598Z  user created (provider: google)
16:20:16.605Z  TypeError on /oauth   (+7 ms)

Isolated experiment output inside the same image:

namespace = "ban"
OK     string   (req.ip)
THROW  ObjectId (req.user._id)  -> TypeError: key.startsWith is not a function
THROW  number                   -> TypeError: key.startsWith is not a function
--- what checkBan builds ---
ipKey   typeof (USE_REDIS=false) = string
userKey typeof (USE_REDIS=false) = object
userKey typeof (USE_REDIS=true)  = string

What browsers are you seeing the problem on?

Server-side, provider-independent. Reproduced with Chrome 152 as the client; no browser-specific behaviour is involved.