Cache Poisoning in Loopback with Firestore
Cache Poisoning in Loopback with Firestore — how this specific combination creates or exposes the vulnerability
Cache poisoning in a Loopback application that uses Google Cloud Firestore as a backend can occur when responses from Firestore are cached based on insufficient or attacker-controlled input. Because Firestore queries often include user-supplied parameters such as document IDs, collection paths, or filter values, an attacker may manipulate these inputs to cause the application to store malicious or incorrect data in the cache.
In a Loopback application, models are typically backed by a data source such as Firestore. If query inputs are not strictly validated and normalized before being used to construct Firestore queries, an attacker can supply crafted parameters that result in unexpected query behavior. For example, an ID parameter that is expected to be a numeric document ID might be replaced with a path like ../other-collection/secret-doc. If the application caches query results using the raw input as part of the cache key, the poisoned cache entry can be served to other users or used in subsequent logic, leading to unauthorized data access or incorrect application state.
Firestore itself does not introduce caching at the serverless function level, but client-side and intermediate caching mechanisms (e.g., in-memory caches, reverse proxies, or application-level caches) may store responses keyed by request parameters. When combined with Loopback’s dynamic model binding and REST or GraphQL endpoint generation, this creates opportunities for cache key collisions if input normalization is inconsistent. An endpoint that accepts a document path parameter and directly passes it to Firestore without validation can inadvertently allow an attacker to influence cache keys across different resources.
Another angle involves metadata or configuration endpoints that expose Firestore collection structures. If these endpoints are cached without proper authorization checks, an attacker may use path traversal or IDOR techniques to poison the cache with sensitive schema information. Because Loopback can auto-generate REST APIs from models, developers might inadvertently expose cacheable endpoints that reflect Firestore metadata without enforcing strict input constraints.
To detect this class of issue, scanning should include tests that supply unexpected or malicious input values, verify that cache keys are normalized and scoped correctly, and confirm that responses are not reused across different contexts. The LLM/AI Security checks available in middleBrick are particularly effective at identifying prompt injection and output leakage patterns that may indicate insufficient isolation between cached responses.
Firestore-Specific Remediation in Loopback — concrete code fixes
Remediation focuses on strict input validation, canonicalization of Firestore paths, and ensuring that cache keys are derived from normalized, context-bound values rather than raw user input. Avoid directly using user-supplied values as document IDs or paths without verification.
Validate and sanitize document paths
Ensure that any document identifier is checked against allowed patterns before being used in a Firestore operation. For example, if a document ID must be a UUID or a numeric string, enforce this explicitly.
const {Firestore} = require('@google-cloud/firestore');
const firestore = new Firestore();
function isValidDocumentId(id) {
// Allow only alphanumeric and hyphen/underscore, 1–500 chars
return typeof id === 'string' && /^[a-zA-Z0-9_-]{1,500}$/.test(id);
}
async function getDocumentSafe(modelName, rawId) {
if (!isValidDocumentId(rawId)) {
throw new Error('Invalid document ID');
}
const docRef = firestore.collection(modelName).doc(rawId);
const doc = await docRef.get();
if (!doc.exists) {
throw new Error('Document not found');
}
return doc.data();
}
Use Firestore references instead of string paths
When constructing references, prefer using Firestore’s built-in methods to avoid path traversal. Do not concatenate user input into a string path.
async function getDocumentByReference(collectionName, documentId) {
// Safe: collection and document IDs are validated separately
const coll = firestore.collection(collectionName);
const doc = await coll.doc(documentId).get();
return doc.data();
}
Normalize cache keys with context and user scope
If caching query results is necessary, derive cache keys from a combination of normalized query parameters, authenticated context, and model schema version. Avoid using raw path inputs directly in cache keys.
function buildCacheKey(userId, collectionName, validatedId) {
// Include user context and schema version to prevent cross-user cache hits
return `v1:user:${userId}:collection:${collectionName}:doc:${validatedId}`;
}
Apply model-level validation in Loopback
Use Loopback’s built-in model validation to enforce constraints on ID properties and prevent malformed input from reaching data sources.
// In your Loopback model JSON configuration or JS file
{
"name": "SecureDocument",
"base": "PersistedModel",
"properties": {
"id": {
"type": "string",
"required": true,
"validate": [
{"name": "isString"},
{"name": "length", "options": {"min": 5, "max": 100}},
{"name": "pattern", "options": {"message": "Invalid format", "pattern": "^[a-zA-Z0-9_-]+$"}}
]
}
}
}
Restrict exposed operations and avoid dynamic resolution
Disable remote methods that accept raw Firestore paths or enable dynamic resolution of document references based on user input. Prefer explicit, typed operations with strict parameter schemas.
// Example: explicitly define a remote method with strict input
SecureDocument.remoteMethod('getById', {
accepts: {arg: 'documentId', type: 'string', required: true},
returns: {arg: 'data', type: 'object', root: true},
http: {path: '/:documentId', verb: 'get'}
});