Automattic/mongoose · error · MongooseError

${key} is not allowed with sanitizeFilter

Error message

${key} is not allowed with sanitizeFilter

What it means

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.

Source

Thrown at lib/helpers/query/sanitizeFilter.js:28

  }
  if (Array.isArray(filter)) {
    for (const subfilter of filter) {
      sanitizeFilter(subfilter);
    }
    return filter;
  }

  const filterKeys = Object.keys(filter);
  for (const key of filterKeys) {
    const value = filter[key];
    if (value?.[trustedSymbol]) {
      continue;
    }
    if (key === '$and' || key === '$or' || key === '$nor') {
      sanitizeFilter(value);
      continue;
    } else if (key === '$jsonSchema' || key === '$where' || key === '$expr' || key === '$text') {
      throw new MongooseError(key + ' is not allowed with sanitizeFilter');
    }

    if (hasDollarKeys(value)) {
      const keys = Object.keys(value);
      if (keys.length === 1 && keys[0] === '$eq') {
        continue;
      }
      filter[key] = { $eq: filter[key] };
    }
  }

  return filter;
};

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Mark server-built filters as trusted: Model.find({ $expr: mongoose.trusted({ $gt: ['$a', '$b'] }) })
  2. Sanitize only the user-supplied fragments, then merge them with trusted operator objects after sanitization
  3. Rewrite the query without the rejected operator (replace $where with plain field conditions)
  4. As a last resort, disable the sanitizer for that one query: { sanitizeFilter: false }

Example fix

// before
mongoose.set('sanitizeFilter', true);
await Model.find({ $expr: { $gt: ['$endDate', '$startDate'] } }); // throws

// after
await Model.find({ $expr: mongoose.trusted({ $gt: ['$endDate', '$startDate'] }) });
Defensive patterns

Strategy: validation

Validate before calling

const SANITIZE_FORBIDDEN = new Set(['$where', '$expr', '$jsonSchema', '$text']);
function assertSanitizable(filter) {
  for (const k of Object.keys(filter)) {
    if (SANITIZE_FORBIDDEN.has(k)) throw new Error(`${k} cannot be used with sanitizeFilter; wrap with mongoose.trusted()`);
    if (k === '$and' || k === '$or' || k === '$nor') filter[k].forEach(assertSanitizable);
  }
}

Type guard

const needsTrusted = (filter) => Object.keys(filter).some(k => ['$where', '$expr', '$jsonSchema', '$text'].includes(k));

Try / catch

try {
  await Model.find(mongoose.sanitizeFilter(filter));
} catch (err) {
  if (err instanceof mongoose.MongooseError && /not allowed with sanitizeFilter/.test(err.message)) {
    // split user input from server-side $expr/$where and re-run with mongoose.trusted()
  } else throw err;
}

Prevention

When it happens

Trigger: 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().

Common situations: 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.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/37607930842f6116. Report an issue: GitHub.