HIGH nosql injectionexpressfirestore

Nosql Injection in Express with Firestore

Nosql Injection in Express with Firestore — how this specific combination creates or exposes the vulnerability

NoSQL injection in an Express service that uses Google Cloud Firestore occurs when user-controlled input is concatenated into queries or document paths without validation or parameterization. Firestore’s query and document APIs accept strings and maps; if an attacker can influence keys, collection names, document IDs, or filter values, they can change the intended query scope or read data they should not access.

Consider an Express route that builds a query from request query parameters:

// Unsafe: directly using query parameters in Firestore access
app.get('/users', async (req, res) => {
  const { role, status } = req.query;
  const base = db.collection('users');
  let query = base;
  if (role) query = query.where('role', '==', role);
  if (status) query = query.where('status', '==', status);
  const snapshot = await query.get();
  res.json(snapshot.docs.map(d => d.data()));
});

Although where uses structured values, an attacker can still influence document access by supplying a malicious document ID in a parameter used elsewhere, or by abusing field traversal if the application dynamically builds collection names:

// Unsafe: collection name derived from user input
app.get('/data/:collection', async (req, res) => {
  const coll = db.collection(req.params.collection);
  const docs = await coll.get();
  res.json(docs.docs.map(d => d.data()));
});

Here, an attacker can set collection to any collection the service account can read, such as internal or sensitive collections, effectively performing horizontal or vertical privilege escalation via NoSQL injection. Similarly, using user input to construct document paths can lead to path traversal across partitions:

// Unsafe: document ID from user input without validation
app.get('/posts/:id/comments', async (req, res) => {
  const docRef = db.doc(`posts/${req.params.id}/comments/public`);
  const doc = await docRef.get();
  res.json(doc.data());
});

If id contains special characters or path segments (e.g., postId/../admin/comments), Firestore may normalize the path in unexpected ways depending on client library behavior, potentially exposing data outside the intended scope. While Firestore does not support the rich injection syntax of some NoSQL databases (e.g., MongoDB’s $ne or $where), query manipulation via structured inputs remains possible, especially when field keys themselves are dynamic:

// Unsafe: dynamic field key from user input
app.get('/search', async (req, res) => {
  const { field, value } = req.query;
  const query = db.collection('products').where(field, '==', value);
  const snapshot = await query.get();
  res.json(snapshot.docs.map(d => d.data()));
});

An attacker can supply field as an array or a compound path (e.g., metadata.admin) to probe nested maps, effectively performing enumeration or unauthorized filtering. Because Firestore indexes map keys, such inputs can yield existence-based side channels or data exposure. Compounded with misconfigured security rules or overly permissive service account permissions, these NoSQL injection patterns can lead to mass data enumeration or unauthorized access across unrelated tenants.

Firestore-Specific Remediation in Express — concrete code fixes

Remediation centers on strict input validation, avoiding direct concatenation of user input into collection names, document paths, or query constraints, and leveraging Firestore’s structured query parameters. Always treat user input as opaque and map it to a controlled allowlist.

1) Use allowlists for collection names and validate document IDs:

const ALLOWED_COLLECTIONS = new Set(['users', 'posts', 'comments']);

app.get('/data/:collection', async (req, res) => {
  if (!ALLOWED_COLLECTIONS.has(req.params.collection)) {
    return res.status(400).json({ error: 'invalid collection' });
  }
  const coll = db.collection(req.params.collection);
  const docs = await coll.get();
  res.json(docs.docs.map(d => d.data()));
});

2) Parameterize queries and avoid dynamic field keys; if dynamic filtering is required, map incoming keys to known fields:

const ALLOWED_FIELDS = new Set(['role', 'status', 'email']);

app.get('/users', async (req, res) => {
  const base = db.collection('users');
  let query = base;
  for (const [key, value] of Object.entries(req.query)) {
    if (ALLOWED_FIELDS.has(key) && value !== undefined) {
      query = query.where(key, '==', value);
    }
  }
  const snapshot = await query.get();
  res.json(snapshot.docs.map(d => d.data()));
});

3) Sanitize document IDs and avoid path traversal by validating format and using Firestore’s built-in reference construction:

function isValidDocumentId(id) {
  return typeof id === 'string' && /^[a-zA-Z0-9_-]{1,100}$/.test(id);
}

app.get('/posts/:id/comments', async (req, res) => {
  const id = req.params.id;
  if (!isValidDocumentId(id)) {
    return res.status(400).json({ error: 'invalid document id' });
  }
  const docRef = db.doc(`posts/${id}/comments/public`);
  const doc = await docRef.get();
  res.json(doc.exists ? doc.data() : null);
});

4) Enforce security rules on the Firestore side as a defense-in-depth layer, but do not rely on them for authorization in the API layer. Treat the Express service as the authoritative policy enforcement point. Combine these practices with the middleBrick CLI to validate your endpoints during development: middlebrick scan <url>. For teams seeking continuous coverage, the middleBrick Pro plan supports 100 APIs with continuous monitoring and GitHub Action integration to fail builds when risk scores degrade.

Frequently Asked Questions

Can NoSQL injection in Firestore expose entire collections?
Yes, if user input is used to construct collection names or document paths without strict allowlists, an attacker can enumerate or access collections they should not see. Always validate collection names and document IDs server-side.
Does Firestore’s query API prevent injection like SQL does?
Firestore does not support expression injection like some NoSQL databases, but query constraints can still be manipulated via dynamic field keys or collection names. Parameterization and strict allowlists are required to prevent unauthorized data access.