HIGH xpath injectionexpressfirestore

Xpath Injection in Express with Firestore

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

XPath Injection occurs when untrusted data is concatenated into an XPath expression without proper sanitization or parameterization, leading to altered query logic. In an Express application using Firestore, this typically arises when a developer builds client-side queries or server-side logic that incorporates user-controlled input into selectors or query constraints expressed as XPath-like strings. Although Firestore does not use XPath natively, applications may construct document paths, collection-group queries, or security rule logic using string concatenation that mirrors XPath patterns (for example, dynamic segment traversal such as users/{uid}/profile built from request parameters). If an attacker can inject path segments or condition-like fragments, they may traverse to unauthorized resources, bypass intended scoping, or expose sensitive data through over-permissive rules.

An Express route that builds a Firestore document reference from user input is vulnerable when it uses string interpolation to assemble paths. For example, an endpoint that retrieves a user’s settings by concatenating a request parameter into a path allows an attacker to manipulate the effective document or collection accessed. Consider a route that reads req.query.docPath and appends it to a base path: const docRef = db.collection('orgs').doc(orgId).collection('settings').doc(req.query.docPath); If docPath is user-supplied and not validated, an attacker can provide a value such as ../../otherOrg/secret, potentially escaping the intended subcollection and accessing data belonging to another organization. Even when Firestore security rules are used, overly permissive rules combined with dynamic path construction can amplify the impact by allowing unintended reads or writes.

Another scenario involves constructing Firestore queries using string fragments that resemble XPath-like predicates. For instance, building a query string such as field == 'value' and admin == true by concatenating user input can shift the intended filter logic. If the application merges these fragments into a broader rule evaluation or into client-side SDK usage, the injected logic can change the result set, exposing documents that should remain restricted. In environments where the Express backend passes partially constructed query expressions to the client or uses them to enforce row-level constraints, this can lead to Insecure Direct Object References (IDOR) or privilege escalation across tenant boundaries.

XPath-style traversal can also manifest in metadata or configuration documents that store access control mappings. If an Express service loads rules or mappings using paths derived from user input and these paths are not rigorously validated against a whitelist, attackers can probe the document hierarchy to discover administrative endpoints or configuration blobs. Because Firestore rules are evaluated per request, a maliciously crafted path can interact with rule conditions in unexpected ways, especially when rules rely on string matching or pattern checks that do not strictly delimit segment boundaries. The key risk in the Express + Firestore stack is therefore uncontrolled use of user input in path or query construction that can be coerced into traversing beyond the intended data scope.

Firestore-Specific Remediation in Express — concrete code fixes

To mitigate Xpath Injection risks in an Express service interacting with Firestore, always treat user input as opaque identifiers and avoid string concatenation for path or query construction. Use parameterized approaches and strict validation to ensure that input cannot alter the intended data scope. The following examples demonstrate secure patterns for common Express use cases.

1. Safe document reference resolution with path validation

Instead of directly using request parameters in document paths, validate each segment against a known set or pattern, and assemble the reference programmatically using Firestore’s API methods.

const express = require('express');
const { initializeApp } = require('firebase-admin');
initializeApp();
const db = firebaseAdmin.firestore();

const app = express();
app.get('/org/:orgId/settings/:docId', async (req, res) => {
  const { orgId, docId } = req.params;
  // Validate orgId and docId format (e.g., UUID or alphanumeric pattern)
  if (!/^[a-zA-Z0-9_-]{1,30}$/.test(orgId) || !/^[a-zA-Z0-9_-]{1,60}$/.test(docId)) {
    return res.status(400).json({ error: 'invalid identifier' });
  }
  const docRef = db
    .collection('orgs')
    .doc(orgId)
    .collection('settings')
    .doc(docId);
  const snapshot = await docRef.get();
  if (!snapshot.exists) {
    return res.status(404).json({ error: 'not found' });
  }
  res.json(snapshot.data());
});

2. Query construction using field values, not path injection

When filtering documents by field values, use Firestore’s native query methods rather than assembling predicate strings. This prevents injected logic from altering the intended query semantics.

app.get('/org/:orgId/users', async (req, res) => {
  const { orgId } = req.params;
  if (!/^[a-zA-Z0-9_-]{1,30}$/.test(orgId)) {
    return res.status(400).json({ error: 'invalid orgId' });
  }
  const users = await db
    .collection('orgs')
    .doc(orgId)
    .collection('users')
    .where('role', '==', 'member')
    .get();
  const result = users.docs.map(d => ({ id: d.id, ...d.data() }));
  res.json(result);
});

3. Rule-aware data scoping with parameterized references

When server-side code must reference tenant-specific data, derive document references using validated identifiers and enforce scoping programmatically, rather than relying on concatenated paths that can be manipulated.

app.post('/org/:orgId/data', async (req, res) => {
  const { orgId } = req.params;
  if (!/^[a-zA-Z0-9_-]{1,30}$/.test(orgId)) {
    return res.status(400).json({ error: 'invalid orgId' });
  }
  const payload = req.body;
  const docRef = db.collection('orgs').doc(orgId).collection('data').doc();
  await docRef.set({ ...payload, createdAt: new Date() });
  res.status(201).json({ id: docRef.id });
});

4. Avoid passing user-controlled expressions to client SDKs

Never construct query filter strings or document paths on the client or via unchecked parameters. Keep all path assembly server-side with strict validation. If you must support flexible queries, use a controlled set of allowed fields and map user input to canonical field names server-side.

// Server-side controlled mapping
const allowedFields = new Set(['name', 'email', 'status']);
app.get('/search', async (req, res) => {
  const { field, value, orgId } = req.query;
  if (!allowedFields.has(field) || typeof value !== 'string') {
    return res.status(400).json({ error: 'invalid query' });
  }
  const col = db.collection('orgs').doc(orgId).collection('profiles');
  const snapshot = await col.where(field, '==', value).limit(10).get();
  res.json(snapshot.docs.map(d => ({ id: d.id, ...d.data() })));
});

These practices ensure that document references and query constraints remain predictable and scoped, reducing the risk of path-based traversal or injection in an Express + Firestore stack.

Frequently Asked Questions

Can Firestore security rules alone prevent XPath Injection-like traversal in Express?
Security rules can restrict access, but they cannot fully prevent path-based traversal if the Express code constructs document references using unchecked user input. Always validate and parameterize paths server-side; rules are a secondary layer.
Does middleBrick detect XPath Injection patterns in API specs and runtime?
middleBrick scans API endpoints for injection-like issues such as IDOR and improper input validation that can enable traversal. While it does not test with an XPath engine, it identifies related authorization and input validation findings that map to risks similar to XPath Injection in Express/Firestore contexts.