Vulnerable Components in Feathersjs with Firestore
Vulnerable Components in Feathersjs with Firestore — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for real-time APIs that typically exposes services through REST and WebSocket endpoints. When FeathersJS uses Google Cloud Firestore as a service adapter, developers often configure service hooks and queries that inadvertently create authorization and validation vulnerabilities. A common pattern is mapping incoming query parameters directly to Firestore queries without sanitization, which can enable BOLA/IDOR and BFLA through relaxed ownership checks.
Consider a FeathersJS service defined with the Firebase adapter, where the before hook does not enforce record ownership. An attacker can manipulate query filters to access documents they should not see, because Firestore rules may be permissive for read operations while the application layer lacks strict tenant or user scoping. This misalignment between Firestore security rules and FeathersJS service logic results in insecure direct object references, a key vector in OWASP API Top 10 Broken Object Level Authorization.
Input validation weaknesses emerge when FeathersJS passes untrusted parameters into Firestore queries. For example, a where clause built from user-controlled fields without type or enum validation can be coerced into returning sensitive data or triggering errors that leak schema details. Firestore’s index and query flexibility may allow unintended field access if the application does not explicitly restrict which fields are queryable.
Property-level authorization gaps arise when Firestore documents contain privileged fields such as isAdmin or role. If FeathersJS returns entire documents to the client, these fields may be exposed even when they should be restricted. This contributes to data exposure and can violate compliance mappings such as PCI-DSS and SOC2, which require strict segregation of privilege-sensitive attributes.
Finally, unsafe consumption patterns occur when FeathersJS services accept raw request bodies and directly write them to Firestore without schema enforcement. Missing content-type validation and field sanitization can lead to injection-like behaviors or document structure corruption. Combined with missing rate limiting, this may allow rapid document writes that resemble abuse or enumeration, triggering noisy alerts but not preventing misuse.
Firestore-Specific Remediation in Feathersjs — concrete code fixes
To secure FeathersJS with Firestore, enforce strict ownership checks in hooks and parameterize queries to avoid dynamic filter building from user input. Below is a hardened FeathersJS service setup that demonstrates these principles.
const feathers = require('@feathersjs/feathers');
const express = require('@feathsjs/express');
const {Firestore} = require('@google-cloud/firestore');
const {FirestoreService} = require('feathers-google-firestore');
const app = feathers();
const firestore = new Firestore();
app.configure(express.json());
// Helper to extract user ID from authenticated request
const getUserId = (params) => {
if (params && params.user && params.user.userId) {
return params.user.userId;
}
throw new Error('Unauthenticated');
};
// Custom before hook to enforce ownership and strict filtering
app.use('/messages', {
async before(hook) {
const userId = getUserId(hook.params);
// Ensure query is scoped to the requesting user
if (hook.params.query) {
hook.params.query.where = hook.params.query.where || [];
// Append tenant/user filter if not already present
const hasUserFilter = hook.params.query.where.some(
cond => cond.field === 'userId' && cond.op === '==' && cond.value === userId
);
if (!hasUserFilter) {
hook.params.query.where.push({ field: 'userId', op: '==', value: userId });
}
} else {
hook.params.query = { where: [{ field: 'userId', op: '==', value: userId }] };
}
// Explicitly limit returned fields to avoid privilege escalation via property exposure
if (!hook.params.query.select) {
hook.params.query.select = ['id', 'text', 'userId', 'createdAt'];
}
return hook;
},
// Use the Firestore service adapter
service: FirestoreService({
Model: firestore.collection('messages')
}),
// After hook to remove sensitive fields before sending response
after: {
all: [async (hook) => {
if (Array.isArray(hook.result.data)) {
hook.result.data = hook.result.data.map((record) => {
const { isAdmin, role, ...safe } = record;
return safe;
});
} else if (hook.result.data) {
const { isAdmin, role, ...safe } = hook.result.data;
hook.result.data = safe;
}
return hook;
}]
}
});
// Example of a parameterized query that avoids raw user input in field names
app.service('/messages').hooks({
before: {
async find(hook) {
const { userId } = hook.params.query;
if (userId) {
// Validate userId format before using it
if (!/^[A-Za-z0-9_-]{1,20}$/.test(userId)) {
throw new Error('Invalid user identifier');
}
// Firestore query with explicit field and value
hook.params.query.where = [
{ field: 'userId', op: '==', value: userId }
];
}
return hook;
}
}
});
These examples show how to bind Firestore queries to a user context, limit returned fields, and validate identifiers to prevent injection and privilege escalation. For compliance mappings, ensure that findings from middleBrick scans are reviewed against OWASP API Top 10 and relevant regulatory controls; middleBrick’s dashboard and CLI reports can help track these issues over time.
Additionally, enable Firestore rules that mirror application-level constraints, such as allowing reads only when request.auth.uid matches the document’s userId field. Combine this with middleBrick’s continuous monitoring in the Pro plan to detect regressions in your API security posture as your FeathersJS service evolves.