{"record":{"id":"37607930842f6116","repo":"Automattic/mongoose","slug":"key-is-not-allowed-with-sanitizefilter","errorCode":null,"errorMessage":"${key} is not allowed with sanitizeFilter","messagePattern":"(.+?) is not allowed with sanitizeFilter","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/helpers/query/sanitizeFilter.js","lineNumber":28,"sourceCode":"  }\n  if (Array.isArray(filter)) {\n    for (const subfilter of filter) {\n      sanitizeFilter(subfilter);\n    }\n    return filter;\n  }\n\n  const filterKeys = Object.keys(filter);\n  for (const key of filterKeys) {\n    const value = filter[key];\n    if (value?.[trustedSymbol]) {\n      continue;\n    }\n    if (key === '$and' || key === '$or' || key === '$nor') {\n      sanitizeFilter(value);\n      continue;\n    } else if (key === '$jsonSchema' || key === '$where' || key === '$expr' || key === '$text') {\n      throw new MongooseError(key + ' is not allowed with sanitizeFilter');\n    }\n\n    if (hasDollarKeys(value)) {\n      const keys = Object.keys(value);\n      if (keys.length === 1 && keys[0] === '$eq') {\n        continue;\n      }\n      filter[key] = { $eq: filter[key] };\n    }\n  }\n\n  return filter;\n};\n","sourceCodeStart":10,"sourceCodeEnd":42,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/helpers/query/sanitizeFilter.js#L10-L42","documentation":"Mongoose's query sanitizer (mongoose.sanitizeFilter() or the sanitizeFilter: true option) wraps operator-bearing values in $eq to block query injection from untrusted objects. It hard-rejects $where and $expr (they allow JavaScript/expression evaluation) and $jsonSchema/$text (top-level operators that cannot be safely wrapped), throwing a MongooseError when any of them appears in a filter that is being sanitized.","triggerScenarios":"Model.find({ $where: 'this.name === \"x\"' }) or Model.find({ $expr: { $gt: ['$a', '$b'] } }) while sanitizeFilter is enabled via mongoose.set('sanitizeFilter', true), the sanitizeFilter query option, or by passing the filter through mongoose.sanitizeFilter().","commonSituations":"Apps that enable sanitizeFilter globally for security and later add a $expr/$text/$where query (search endpoints are the usual offender); middleware that sanitizes every incoming filter; merging trusted server-built operator filters with user filters under one sanitized call.","solutions":["Mark server-built filters as trusted: Model.find({ $expr: mongoose.trusted({ $gt: ['$a', '$b'] }) })","Sanitize only the user-supplied fragments, then merge them with trusted operator objects after sanitization","Rewrite the query without the rejected operator (replace $where with plain field conditions)","As a last resort, disable the sanitizer for that one query: { sanitizeFilter: false }"],"exampleFix":"// before\nmongoose.set('sanitizeFilter', true);\nawait Model.find({ $expr: { $gt: ['$endDate', '$startDate'] } }); // throws\n\n// after\nawait Model.find({ $expr: mongoose.trusted({ $gt: ['$endDate', '$startDate'] }) });","handlingStrategy":"validation","validationCode":"const SANITIZE_FORBIDDEN = new Set(['$where', '$expr', '$jsonSchema', '$text']);\nfunction assertSanitizable(filter) {\n  for (const k of Object.keys(filter)) {\n    if (SANITIZE_FORBIDDEN.has(k)) throw new Error(`${k} cannot be used with sanitizeFilter; wrap with mongoose.trusted()`);\n    if (k === '$and' || k === '$or' || k === '$nor') filter[k].forEach(assertSanitizable);\n  }\n}","typeGuard":"const needsTrusted = (filter) => Object.keys(filter).some(k => ['$where', '$expr', '$jsonSchema', '$text'].includes(k));","tryCatchPattern":"try {\n  await Model.find(mongoose.sanitizeFilter(filter));\n} catch (err) {\n  if (err instanceof mongoose.MongooseError && /not allowed with sanitizeFilter/.test(err.message)) {\n    // split user input from server-side $expr/$where and re-run with mongoose.trusted()\n  } else throw err;\n}","preventionTips":["Apply sanitizeFilter only to user-supplied filter fragments, never to server-composed operator queries","Mark every server-built operator filter with mongoose.trusted() at construction time","Never build $where from user input even without sanitizeFilter — it is a code-execution vector"],"tags":["mongoose","security","query-injection","sanitize-filter"],"backgroundTag":"nosql-injection-protection","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}