Skip to content
Injection Reviewed 2026-09-12

NoSQL Injection

What does this mean ?

NoSQL injection occurs when untrusted input changes a database query's structure or operators. Document databases often accept structured objects, so avoiding SQL strings is not enough. A JSON object supplied where the application expects a string can be interpreted as a query condition.

What can happen ?

A query can match unexpected records, bypass an application filter or become expensive to execute. Injection and authorization bugs can combine when the request is allowed to choose a tenant or owner field. Server-side JavaScript query features create an additional execution boundary.

Recommendation

Accept a narrow input schema. Require scalar strings/numbers where expected, and construct query fields and operators in application code. Never pass the entire request body to a database query or update. Do not evaluate user-provided JavaScript or create $where expressions from request values.

Bind tenant/owner scope to the authenticated principal, not a client field. Limit result count and returned fields, and configure query timeouts. Treat regular-expression search as a separate feature with length/complexity controls; escaping a regex changes its semantics to literal search and does not create authorization.

Sample Code

These examples find a document by exact name for the current owner. ownerId / owner_id comes from verified server-side identity. Validate its expected type in the authentication layer.

// Unsafe: request data can contain database operators and owner fields.
const unsafeFilter = req.body;
function buildDocumentFilter(body, ownerId) {
  if (body === null || typeof body !== 'object' || Array.isArray(body) ||
      Object.keys(body).length !== 1 || !Object.hasOwn(body, 'name') ||
      typeof body.name !== 'string' || body.name.length < 1 || body.name.length > 100) {
    throw new Error('A bounded name string is required');
  }
  return { ownerId, name: { $eq: body.name } };
}

Use the resulting filter with a bounded query in a properly authenticated handler:

const filter = buildDocumentFilter(req.body, req.user.id);
const rows = await collection.find(filter, {
  projection: { name: 1 }, maxTimeMS: 1000
}).limit(25).toArray();

This example uses the MongoDB Node driver, not Mongoose. Send a controlled error response rather than returning raw database exceptions.

PyMongo:

def build_document_filter(body, owner_id):
    if not isinstance(body, dict) or set(body) != {"name"}:
        raise ValueError("Unexpected search fields")
    name = body["name"]
    if not isinstance(name, str) or not 1 <= len(name) <= 100:
        raise ValueError("A bounded name string is required")
    return {"ownerId": owner_id, "name": {"$eq": name}}
query = build_document_filter(body, current_owner_id)
rows = list(collection.find(query, {"name": 1}).limit(25).max_time_ms(1000))

MongoDB's typed filter builder, with a validated string name and server-owned ownerId:

if (string.IsNullOrWhiteSpace(name) || name.Length > 100)
    throw new ArgumentException("A bounded name string is required");
var filter = MongoDB.Driver.Builders<Document>.Filter.And(
    MongoDB.Driver.Builders<Document>.Filter.Eq(item => item.OwnerId, ownerId),
    MongoDB.Driver.Builders<Document>.Filter.Eq(item => item.Name, name));
var options = new MongoDB.Driver.FindOptions
{
    MaxTime = TimeSpan.FromSeconds(1)
};
var rows = await collection.Find(filter, options).Limit(25).ToListAsync();

Import MongoDB.Driver for fluent extensions. Document is the application's fixed model with OwnerId and Name properties. Do not replace the builder with a raw JSON filter constructed by concatenation.

Regression checks

Pass a normal name, then an object, array, null, extra owner field and overlong string. Invalid shapes should fail before the database driver is called. Assert the trusted owner remains in every constructed filter. Against a fixture database, verify a valid record belonging to another owner is not returned and the result limit is applied. No live database or customer data is needed.

References