HIGH injection flawsrestifyfirestore

Injection Flaws in Restify with Firestore

Injection Flaws in Restify with Firestore — how this specific combination creates or exposes the vulnerability

Injection flaws occur when untrusted data is interpreted as part of a command or query. In a Restify service that uses Google Cloud Firestore, the most common pattern is accepting client-supplied values and using them to construct Firestore queries. If these values are not strictly validated, sanitized, or parameterized, an attacker can manipulate query behavior.

Firestore itself does not support traditional SQL-style injection because it is a NoSQL database with a strongly typed query API. However, injection-like issues arise at the application layer when building queries dynamically. For example, using string concatenation or object spread to inject field names, collection paths, or operator values from user input can lead to unintended data access or data leakage.

Consider a Restify endpoint that retrieves a user document by ID:

server.get('/users/:id', async (req, res, next) => {
  const userDoc = await db.collection('users').doc(req.params.id).get();
  res.send(userDoc.data());
});

At first glance this appears safe because the document ID is used as a literal path component. The risk increases when the query structure itself becomes dynamic. An endpoint that filters on a field supplied by the client can become vulnerable:

server.get('/search', async (req, res, next) => {
  const { field, value } = req.query;
  const q = db.collection('products').where(field, '==', value);
  const snapshot = await q.get();
  const results = snapshot.docs.map(d => d.data());
  res.send(results);
});

If field is taken directly from req.query, an attacker can supply a field name that exposes sensitive documents or metadata. For instance, supplying field=__proto__ or other special keys can distort object behavior in JavaScript, potentially bypassing intended filters. More critically, if the application builds more complex dynamic queries using unsanitized input, it may inadvertently allow access to collections or documents that should be restricted.

Another injection pattern involves operator injection. If an endpoint allows the client to specify both field and operator, and the operator is not strictly enumerated, an attacker might supply values such as {'$gt': 0} to manipulate query semantics in unexpected ways. Firestore query constraints require that the operator be one of a fixed set (==, <, <=, >, >=, array-contains, etc.). Allowing raw user input to dictate the operator effectively lets the client reshape the query logic.

Path traversal is also a concern when collection or document IDs are derived from user input. For example, constructing a document reference using concatenation or template strings without strict validation can allow traversal across logical boundaries:

const docPath = `users/${userId}/data/${documentId}`;
const doc = await db.doc(docPath).get();

If userId or contains path segments such as .. or encoded equivalents, it may reference an unintended document. While Firestore treats the path as a single string, the application logic must ensure that IDs are validated and scoped correctly.

In summary, injection flaws with Restify and Firestore stem from dynamically constructing queries, field names, operators, or document paths using unsanitized client input. The database does not execute arbitrary code, but the application can be coerced into reading or querying data in unintended ways, leading to information exposure or privilege escalation.

Firestore-Specific Remediation in Restify — concrete code fixes

Remediation centers on strict input validation, whitelisting, and avoiding dynamic query construction. Below are concrete, Firestore-specific patterns for Restify that reduce injection risk.

1. Use a strict allowlist for field names

Do not trust client-supplied field names. Validate against a known set of fields before using them in a query:

const ALLOWED_FIELDS = new Set(['name', 'price', 'category', 'createdAt']);

server.get('/search', async (req, res, next) => {
  const { field, value } = req.query;
  if (!ALLOWED_FIELDS.has(field)) {
    return next(new Error('Invalid field')); // or return a 400 response
  }
  const q = db.collection('products').where(field, '==', value);
  const snapshot = await q.get();
  const results = snapshot.docs.map(d => d.data());
  res.send(results);
});

2. Use a fixed set of validated operators

If your API must support dynamic operators, map user input to known safe values:

const OPERATOR_MAP = {
  eq: '==',
  gt: '>',
  gte: '>=',
  lt: '<',
  lte: '<=',
  in: 'in'
};

server.get('/advanced-search', async (req, res, next) => {
  const { field, op, value } = req.query;
  const operator = OPERATOR_MAP[op];
  if (!operator || !ALLOWED_FIELDS.has(field)) {
    return next(new Error('Invalid operator or field'));
  }
  // Firestore requires using the operator constant from the SDK
  // This example assumes a mapping to the actual Firestore operator
  const q = db.collection('products').where(field, operator, Number(value));
  const snapshot = await q.get();
  res.send(snapshot.docs.map(d => d.data()));
});

3. Validate and scope document paths

Ensure IDs are alphanumeric (or match an expected pattern) and avoid string concatenation for paths. Use a helper to construct safe references:

function safeDocPath(userId, documentId) {
  if (!/^[a-zA-Z0-9_-]+$/.test(userId) || !/^[a-zA-Z0-9_-]+$/.test(documentId)) {
    throw new Error('Invalid ID');
  }
  return db.doc(`users/${userId}/data/${documentId}`);
}

server.get('/data/:userId/:documentId', async (req, res, next) => {
  const docRef = safeDocPath(req.params.userId, req.params.documentId);
  const doc = await docRef.get();
  if (!doc.exists) {
    return next(new Error('Not found'));
  }
  res.send(doc.data());
});

4. Prefer parameterized queries and avoid raw eval

Never construct Firestore queries by evaluating strings or using Function. Stick to the SDK’s methods with explicit parameters.

5. Use middleware to normalize and sanitize inputs

Add a validation layer before handlers run. For example, using a simple validator:

function validateQuery(req, res, next) {
  const { field } = req.query;
  if (field && !/^[a-z][a-zA-Z0-9]*$/.test(field)) {
    return res.status(400).send({ error: 'Invalid query parameter' });
  }
  next();
}

server.get('/search', validateQuery, async (req, res, next) => {
  const q = db.collection('products').where(req.query.field, '==', req.query.value);
  const snapshot = await q.get();
  res.send(snapshot.docs.map(d => d.data()));
});

These patterns ensure that Firestore queries remain predictable and that user input cannot alter the intended scope or structure of the query.

Frequently Asked Questions

Can Firestore queries be vulnerable to injection if field names are not validated?
Yes. If field names or operator values are taken directly from user input without strict allowlisting, an attacker can influence query behavior, potentially accessing unintended documents or causing data leakage.
Does middleBrick detect injection-like risks in API endpoints that use Firestore?
middleBrick runs 12 parallel security checks, including input validation and property authorization, which can identify risky query construction patterns and unsafe usage of Firestore in your Restify endpoints.