HIGH api rate abusefeathersjsfirestore

Api Rate Abuse in Feathersjs with Firestore

Api Rate Abuse in Feathersjs with Firestore — how this specific combination creates or exposes the vulnerability

FeathersJS is a minimal, real-time web framework that exposes services as REST and WebSocket endpoints. When paired with Cloud Firestore as the persistence layer, developers often rely on Firestore’s built-in quotas and pricing controls to manage load. However, these backend safeguards do not substitute for application-layer rate limiting, and the default FeathersJS setup does not enforce request throttling on service methods. This combination creates an exposed attack surface where unauthenticated or low-cost API calls can be issued rapidly, leading to resource exhaustion, inflated Firestore read operations, and potential service disruption.

Rate abuse in this stack typically manifests through two vectors: service endpoint enumeration and high-frequency create/read/update operations. Because FeathersJS services are declarative and map closely to Firestore collections, an attacker who discovers a service name (e.g., /messages) can probe list and find operations without requiring authentication if the service is configured with allowExternal: true or per-method authentication is omitted. Firestore, while scalable, still incurs read and write operations per request; a burst of list queries with filters or collection group scans can consume substantial reads, impacting both cost and latency. In a black-box scan, a FeathersJS endpoint backed by Firestore may reveal excessive data exposure if responses include full documents or sensitive fields, and without rate controls, an attacker can leverage automated scripts to iterate through IDs or paginate through large result sets.

An additional concern involves Firestore rate limits at the project level rather than per client. Firestore provides daily and per-index limits, but these are coarse and not enforced with millisecond precision. A FeathersJS service performing unbounded queries—such as searching across string fields without constraints or using inefficient composite indexes—can trigger project-wide degradation under heavy load. Furthermore, real-time listeners established via FeathersJS WebSocket transport can maintain long-lived connections, enabling an adversary to amplify impact by opening many concurrent listeners that each trigger repeated queries. The scanning methodology used by middleBrick tests these unauthenticated surfaces across 12 parallel checks, including rate limiting and input validation, to identify whether a FeathersJS + Firestore deployment allows rapid, unthrottled enumeration or data extraction that could lead to denial of availability or excessive resource consumption.

In practice, a realistic abuse scenario involves an unauthenticated POST to a FeathersJS create endpoint that writes a document to Firestore while also triggering downstream list queries due to hooks or watchers. If no debouncing or cost caps are applied, an attacker can induce repeated reads and writes, magnifying Firestore operations per second. middleBrick’s LLM/AI Security checks do not apply here, but its rate limiting and input validation tests can surface whether the service lacks request throttling, predictable identifiers, or proper query constraints, which are prerequisites for more severe exploitation pathways such as IDOR or privilege escalation in adjacent services.

Firestore-Specific Remediation in Feathersjs — concrete code fixes

Remediation centers on enforcing application-level rate limits, constraining Firestore queries, and hardening service configurations. Below are concrete, idiomatic examples for a FeathersJS service using the Firebase Admin SDK.

1. Define a token-bucket rate limiter and attach it to the service

Use a shared in-memory store or Redis to track request counts per IP or API key. The example below shows a simple in-memory limiter applied to a FeathersJS before hook, suitable for low-traffic prototypes. For production, integrate with a distributed store to coordinate limits across instances.

// src/hooks/rate-limit.js
const capacity = 30;          // tokens
const refillRate = 2;         // tokens per second
const lastRefill = { ts: Date.now(), tokens: capacity };

function rateLimit(req, res, next) {
  const now = Date.now();
  const elapsed = (now - lastRefill.ts) / 1000;
  lastRefill.tokens = Math.min(capacity, lastRefill.tokens + elapsed * refillRate);
  lastRefill.ts = now;

  if (lastRefill.tokens < 1) {
    return next({
      type: 'FeathersError',
      message: 'Rate limit exceeded',
      code: 429
    });
  }
  lastRefill.tokens -= 1;
  next();
}

module.exports = rateLimit;

In your service definition, add the hook:

const app = require('@feathersjs/feathers')();
const express = require('@feathersjs/express');
const rateLimit = require('./hooks/rate-limit');
const services = require('./services');

const http = express(app);
app.configure(express.rest());
app.configure(express.socketio());

app.use('/messages', services.messages);
app.service('messages').hooks({
  before: {
    all: [rateLimit]
  }
});

2. Constrain Firestore queries with pagination and field selection

Avoid unbounded list operations. Use query limits, cursors, and select only necessary fields to reduce document reads and payload size.

// src/services/messages/messages.class.js
const { Service } = require('feathersjs');
const admin = require('firebase-admin');

class MessagesService extends Service {
  async find(params) {
    const db = admin.firestore();
    let query = db.collection('messages').orderBy('createdAt', 'desc').limit(50);

    if (params.query && params.query.cursor) {
      query = query.startAfter(params.query.cursor);
    }

    const snapshot = await query.get();
    const data = snapshot.docs.map(doc => ({
      id: doc.id,
      ...doc.data()
    }));

    return {
      total: data.length,
      limit: 50,
      data
    };
  }
}

module.exports = function () {
  const app = this;
  app.use('/messages', new MessagesService({ Model: null, paginate: { default: 50, max: 50 } }));
};

3. Validate and sanitize inputs to prevent injection-driven amplification

Ensure query parameters and payload fields are validated to avoid expensive or wide-scope queries. FeathersJS hooks can sanitize inputs before Firestore interaction.

// src/hooks/validate-messages.js
const { BadRequest } = require('@feathersjs/errors');

function validateMessages() {
  return context => {
    const { text, userId } = context.data || {};
    if (typeof text !== 'string' || text.length > 500) {
      throw new BadRequest('Invalid message payload');
    }
    if (typeof userId !== 'string' || !/^[A-Za-z0-9_-]+$/.test(userId)) {
      throw new BadRequest('Invalid user identifier');
    }
    // Ensure only allowed fields are passed to Firestore
    context.data = { text, userId, createdAt: new Date() };
    return context;
  };
}

module.exports = validateMessages;

Attach this hook alongside your before hooks. Combined with the earlier limit and pagination, this reduces the risk of abusive queries that drive up Firestore reads or trigger project-level throttling.

Frequently Asked Questions

Can FeathersJS services with public access be safely exposed alongside Firestore?
No. Publicly exposed FeathersJS services without authentication or rate limiting can allow mass enumeration and excessive Firestore reads. Always apply per-method authentication and request throttling, and avoid broad allowExternal configurations.
Do Firestore quotas replace the need for API rate limiting in FeathersJS?
No. Quotas protect your project billing but do not prevent application-layer abuse such as rapid enumeration or high-frequency calls from a single client. Application-level rate limits and constrained queries are necessary to control real-time behavior and user experience.