Insecure Design in Feathersjs with Firestore
Insecure Design in Feathersjs with Firestore — how this specific combination creates or exposes the vulnerability
Insecure Design in a Feathersjs service that uses Cloud Firestore often originates from misaligned abstractions and permissive rules. Feathers encourages a service-oriented architecture where a service exposes REST and/or Socket endpoints with a minimal interface. When Firestore rules are written broadly to accommodate rapid iteration, the effective authorization boundary may be weaker than the application intends. A Feathers hook that does not re-validate ownership or scope can inadvertently rely on client-supplied identifiers such as userId or userRole, which an attacker can tamper with. Because Firestore security rules run independently of application logic, a design that assumes rules alone enforce multi-tenancy or row-level isolation can be insufficient if rules are misconfigured or if the service does not enforce its own checks.
Consider a design where a single Firestore collection stores documents for multiple tenants, differentiated only by a tenantId field. If the Feathers service accepts a tenantId from the client and uses it directly in queries without verifying that the authenticated caller belongs to that tenant, the design conflates identity with authorization. An attacker can change the tenantId in the request to access another tenant’s data. This is an Insecure Design issue because the system architecture does not enforce tenant isolation at the service layer; it depends on clients to provide the correct scope, which violates the principle of zero-trust.
Another dimension is over-permissive Firestore rules designed to accelerate prototyping. Rules that allow read or write access based only on request.auth != null, without checking document-level attributes, expose a broad attack surface. For example, a rule like allow read: if request.auth != null; permits any authenticated user to read any document in the collection. When combined with Feathers services that return query results without additional filtering, this design can lead to mass data exposure. The service may return sensitive records because the rule does not enforce record ownership or role-based constraints at the database level.
LLM/AI Security aspects also intersect with Insecure Design. If a Feathers service exposes an endpoint that incorporates user input into prompts or passes raw document contents to an LLM endpoint without validation, the design may leak system prompts or expose PII. For instance, concatenating user-supplied fields into a prompt without sanitization can enable prompt injection or cause the system to reveal internal instructions. Output scanning becomes necessary to detect API keys or executable code returned by LLM calls, especially when Firestore documents are used as context for generation. The design must ensure that sensitive data is not inadvertently forwarded to external AI services.
Property Authorization is another critical area. A Feathers service that maps Firestore documents directly to API resources must ensure that each operation checks not just the presence of a field, but whether the caller is authorized for that specific property. For example, a user’s email address or role stored in Firestore should not be editable by other users. If the service relies on Firestore rules to enforce immutability of certain fields while the API accepts full document updates, the design is insecure. The service must validate property-level permissions, especially when rules use wildcard paths or broad conditionals.
Firestore-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on enforcing authorization at the service layer and tightening Firestore rules. In Feathers, use hooks to validate ownership and scope before constructing queries. Never trust client-supplied identifiers for scoping. Instead, derive the tenant or user context from the authenticated subject and use it to constrain queries. Below is a Feathers hook that ensures a user can only access documents belonging to their tenant.
// Feathers hook to enforce tenant isolation
const { iff, isProvider } = require('feathers-hooks-common');
function tenantScopeHook(context) {
if (context.params.user && context.params.user.tenantId) {
// Ensure the query is scoped to the authenticated user's tenant
const query = context.params.query || {};
query.tenantId = context.params.user.tenantId;
context.params.query = query;
} else {
throw new Error('Tenant scope not available');
}
return context;
}
// Apply in a Feathers service
const service = app.service('records');
service.hooks({
before: {
find: [tenantScopeHook],
get: [tenantScopeHook]
}
});
For Firestore rules, adopt least-privilege conditions that reference request.auth.uid and document fields. Avoid broad read rules. Instead, scope reads to documents where the owner matches the authenticated user. For multi-tenant designs, include a tenant identifier check.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /records/{recordId} {
allow read: if request.auth != null && request.auth.uid == request.resource.data.ownerId && request.resource.data.tenantId == request.auth.token.tenantId;
allow write: if request.auth != null && request.auth.uid == request.resource.data.ownerId && request.resource.data.tenantId == request.auth.token.tenantId;
}
}
}
In the service, explicitly filter and validate fields to prevent mass assignment and property escalation. When updating a document, specify which fields are mutable and reject unexpected properties. This complements Firestore rules and reduces the risk of privilege escalation through property manipulation.
// Feathers before hook for property validation
function validateProperties(context) {
const data = context.data || {};
const allowed = ['name', 'value', 'tags'];
Object.keys(data).forEach(key => {
if (!allowed.includes(key)) {
throw new Error(`Property ${key} is not allowed`);
}
});
// Ensure ownerId is not user-controlled in updates
if (data.ownerId) {
delete data.ownerId;
}
return context;
}
const securedService = app.service('records');
securedService.hooks({
before: {
create: [validateProperties],
patch: [validateProperties]
}
});
When integrating LLM/AI features, ensure that Firestore documents used as context are sanitized and that sensitive fields are omitted before being sent to external endpoints. Implement server-side filtering to strip PII and secrets. This design prevents inadvertent leakage through prompts and aligns with data exposure controls.
// Example: sanitize Firestore data before LLM consumption
function sanitizeForLLM(doc) {
const { apiKey, internalNotes, ...safe } = doc;
return safe;
}
async function getContextForLLM(recordId) {
const doc = await firestore.collection('records').doc(recordId).get();
if (!doc.exists) throw new Error('Not found');
return sanitizeForLLM(doc.data());
}
These combined measures address Insecure Design by embedding authorization in the service, tightening Firestore rules, validating properties, and protecting sensitive data when used with AI components. The result is a more robust boundary between identity, tenant scope, and permissions.