Skip to content

Security finding: MongoDB operator injection in find and deleteOne tools #18

@piiiico

Description

@piiiico

Summary

We scanned mongo-mcp using agent-audit and found that the find and deleteOne tools pass unvalidated MongoDB query operators directly to the driver, enabling operator injection attacks.

OWASP Agentic AI Top 10: A03 - Insufficient Input/Output Validation


Finding: MongoDB Operator Injection via Unfiltered Query Filters

Files:

  • src/tools/documents/find.ts — line 47
  • src/tools/documents/delete-one.ts — line 32

Both tools accept a filter object from the MCP tool call and pass it directly to the MongoDB driver with no operator filtering:

// src/tools/documents/find.ts:42-50
async execute(params: FindParams) {
  const collection = this.validateCollection(params.collection);
  const results = await db
    .collection(collection)
    .find(params.filter || {})   // ← filter passed directly, no operator validation
    .project(params.projection || {})
    .limit(Math.min(params.limit || 10, 1000))
    .toArray();
// src/tools/documents/delete-one.ts:28-32
async execute(params: DeleteOneParams) {
  const collection = this.validateCollection(params.collection);
  const filter = this.validateObject(params.filter, "Filter");
  const result = await db.collection(collection).deleteOne(filter);  // ← direct pass

validateObject (in base/tool.ts) only checks that the value is an object — it does not filter MongoDB operators.

Attack Vectors

1. ReDoS via $regex operator (any MongoDB version)

An AI agent receiving a malicious prompt could call find with:

{ "filter": { "name": { "$regex": "^(a+)+$", "$options": "i" } } }

The catastrophic backtracking regex would block the MongoDB thread pool, causing server-side denial of service.

2. JavaScript execution via $where (MongoDB instances with javascriptEnabled: true)

On MongoDB deployments that have not explicitly disabled JavaScript execution:

{ "filter": { "$where": "function() { sleep(5000); return true; }" } }

$where is disabled by default in MongoDB 4.4+, but many deployments still use older versions or have security.javascriptEnabled: true set explicitly.

3. Authentication bypass pattern via $ne/$gt

{ "filter": { "role": { "$ne": "user" } } }

Can be used to enumerate privileged documents by querying "everything that is NOT user".

4. Recursive operator nesting for query amplification

Deeply nested operators like $or, $and, $not can be nested to force O(n^k) query plans, causing resource exhaustion.


Why This Matters for MCP

MCP tools receive inputs from AI agents. A document the AI reads, a webpage it visits, or a user message can contain a prompt injection payload that causes the AI to call find or deleteOne with a crafted MongoDB operator. Since the filter is passed directly to the driver, any valid MongoDB operator the AI sends will execute.


Suggested Fix

Implement a recursive operator allowlist or blocklist before executing the query:

const BLOCKED_OPERATORS = new Set(['$where', '$function', '$accumulator']);

function sanitizeFilter(filter: Record<string, unknown>): Record<string, unknown> {
  for (const key of Object.keys(filter)) {
    if (BLOCKED_OPERATORS.has(key)) {
      throw new McpError(ErrorCode.InvalidRequest, `Operator ${key} is not permitted`);
    }
    const value = filter[key];
    if (value && typeof value === 'object' && !Array.isArray(value)) {
      sanitizeFilter(value as Record<string, unknown>);
    }
  }
  return filter;
}

For $regex specifically, validate that the pattern is not catastrophically backtracking using a safe-regex library before executing the query.


Found using agent-audit — static analysis for MCP servers. OWASP Agentic AI Top 10 reference: A03:2025 Insufficient Input/Output Validation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions