{"record":{"id":"0ff3a8034c44cd92","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-query-0ff3a8","errorCode":"error-invalid-query","errorMessage":"isValidQuery.errors.join('\\n')","messagePattern":"isValidQuery\\.errors\\.join\\('\\\\n'\\)","errorType":"validation","errorClass":"Meteor.Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/users.ts","lineNumber":719,"sourceCode":"\t\t\t// if user provided a query, validate it with their allowed operators\n\t\t\t// otherwise we use the default query (with $regex and $options)\n\t\t\tif (\n\t\t\t\t!isValidQuery(\n\t\t\t\t\tnonEmptyQuery,\n\t\t\t\t\t[\n\t\t\t\t\t\t...inclusiveFieldsKeys,\n\t\t\t\t\t\tinclusiveFieldsKeys.includes('emails') && 'emails.address.*',\n\t\t\t\t\t\tinclusiveFieldsKeys.includes('username') && 'username.*',\n\t\t\t\t\t\tinclusiveFieldsKeys.includes('name') && 'name.*',\n\t\t\t\t\t\tinclusiveFieldsKeys.includes('type') && 'type.*',\n\t\t\t\t\t\tinclusiveFieldsKeys.includes('customFields') && 'customFields.*',\n\t\t\t\t\t].filter(Boolean) as string[],\n\t\t\t\t\t// At this point, we have already validated the user query not containing malicious fields\n\t\t\t\t\t// On here we are using our own query so we can allow some extra fields\n\t\t\t\t\t[...this.queryOperations, '$regex', '$options'],\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tthrow new Meteor.Error('error-invalid-query', isValidQuery.errors.join('\\n'));\n\t\t\t}\n\n\t\t\tconst hidden = await getUsersHiddenFrom(this.userId);\n\n\t\t\tif (hidden && queryFiltersStatus(query)) {\n\t\t\t\tnonEmptyQuery.$and = [...(nonEmptyQuery.$and ?? []), { _id: { $nin: [...hidden] } }];\n\t\t\t}\n\n\t\t\tconst actualSort = sort || { username: 1 };\n\n\t\t\tif (sort?.status) {\n\t\t\t\tactualSort.active = sort.status;\n\t\t\t}\n\n\t\t\tif (sort?.name) {\n\t\t\t\tactualSort.nameInsensitive = sort.name;\n\t\t\t}\n","sourceCodeStart":701,"sourceCodeEnd":737,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/e4b8178b205510181a96ceefee043d0abcd13e5a/apps/meteor/server/api/v1/users.ts#L701-L737","documentation":"Thrown by GET /api/v1/users.list when the `query` query-param (parsed by parseJsonQuery) fails isValidQuery validation. The server only permits filtering on field keys derived from the projection (plus aliases like emails.address.*) and only the operators $or/$and/$regex/$options. The message is isValidQuery.errors.join('\\n'), so it lists every offending field or operator.","triggerScenarios":"Calling users.list with ?query={\"emails.address\":{\"$in\":[...]}} (operator $in not allowed), ?query={\"roles\":\"admin\"} (roles not in the allowed field list for the projection), or a non-object/misspelled query value. Also triggered when `fields` projection keys and query keys are inconsistent, since allowed query fields are derived from inclusiveFieldsKeys.","commonSituations":"Clients copy a MongoDB query that works in mongo shell into the REST query param; scripts written against older Rocket.Chat versions that accepted arbitrary operators; adding customFields to the query without including customFields in the fields projection.","solutions":["Read the joined isValidQuery errors in the response body — they name the exact disallowed field/operator","Restrict operators to $or/$and/$regex/$options and use regex strings instead of $in/$nin/$gt","Ensure every key in query appears in the fields projection (e.g. add \"fields\":{\"username\":1} when querying username)","Test the exact URL-encoded JSON of the query param with curl before wiring it into code"],"exampleFix":"// before\nGET /api/v1/users.list?query={\"emails.address\":{\"$in\":[\"a@b.c\"]}}\n// after\nGET /api/v1/users.list?fields={\"emails\":1}&query={\"emails.address\":{\"$regex\":\"^a@b.c$\",\"$options\":\"i\"}}","handlingStrategy":"validation","validationCode":"const allowedOps = new Set(['$or','$and','$regex','$options']);\nconst q = JSON.parse(rawQuery);\nconst ok = Object.entries(q).every(([k,v]) => (k.startsWith('$') ? allowedOps.has(k) : projectionKeys.includes(k)) && (v == null || typeof v !== 'object' || Object.keys(v).every((op) => op.startsWith('$') ? allowedOps.has(op) : true)));\nif (!ok) throw new Error('query uses disallowed field/operator');","typeGuard":"const isAllowedQuery = (q: unknown, fields: string[]): q is Record<string, unknown> =>\n  typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).every((k) => fields.includes(k) || ['$or','$and'].includes(k));","tryCatchPattern":"try { await sdk.get('users.list', { query: rawQuery }); } catch (e) { if (e.response?.data?.errorType === 'error-invalid-query') { console.error(e.response.data.details ?? e.response.data.message); /* strip bad ops, retry once */ } else throw e; }","preventionTips":["Derive query keys from the same fields projection you send","Never paste raw mongo shell queries into the query param","URL-encode JSON.stringify(query) exactly once","Keep a unit test fixture of known-good query strings per endpoint"],"tags":["rest-api","mongo-query","validation","users"],"backgroundTag":"mongo-query-validation-failed","analyzedSha":"e4b8178b205510181a96ceefee043d0abcd13e5a","analyzedAt":"2026-08-21T15:01:34.830Z","contentChangedAt":"2026-08-21T15:01:34.830Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}