Injection Flaws in Adonisjs with Firestore
Injection Flaws in Adonisjs with Firestore — how this specific combination creates or exposes the vulnerability
AdonisJS is a Node.js web framework that encourages structured request handling and validation, while Cloud Firestore is a managed document database. When these two are combined without strict input governance, injection-style risks can still arise—not in the form of traditional SQL injection, but through unsafe data construction, misuse of Firestore APIs, and insufficient validation of user-controlled inputs used to build queries, document paths, or Firestore queries.
One common pattern is building a query by directly concatenating user input into a reference or using unchecked parameters to filter documents. For example, using req.params.slug to construct a document path without normalization or validation can expose path traversal or unexpected document access patterns.
// Risky: using unchecked user input in document path resolution
const docRef = db.collection('tenants').doc(req.params.tenantId).collection('configs').doc(req.params.configKey);
const snapshot = await docRef.get();
If tenantId or configKey are not validated, an attacker can probe namespace boundaries or attempt access to unrelated collections by injecting dot notation or reserved paths. AdonisJS route parameters and query strings must always be treated as untrusted.
Another injection-like risk emerges when user input is used to dynamically build Firestore queries without strict allowlisting of fields and operators. Consider filtering documents by a user-supplied field name or operator:
// Risky: dynamic field and operator from user input
const field = req.input('field');
const operator = req.input('op');
const value = req.input('value');
const query = db.collection('events').where(field, operator, value);
const snapshot = await query.get();
This pattern can lead to query manipulation, data exfiltration across partitions, or bypass of intended access controls. Firestore does not support an `$where`-style unrestricted predicate, but unsafe construction can still expose unintended subsets of data or enable denial-of-query scenarios.
LLM/AI Security checks in middleBrick detect system prompt leakage and active prompt injection probes; while Firestore does not execute prompts, analogous risks occur when prompts or generated content are stored unsafely. For instance, storing raw user messages or AI outputs without sanitization can lead to injection of executable code or PII into document fields, which downstream services or LLM consumers may re-interpret.
Additionally, insecure API exposure can occur when an AdonisJS endpoint exposes Firestore document IDs or references without proper authorization checks. BOLA/IDOR patterns often map to predictable document IDs derived from user-controlled input, enabling horizontal privilege escalation across tenant or user boundaries.
Firestore-Specific Remediation in Adonisjs — concrete code fixes
Remediation centers on strict input validation, allowlisting, and avoiding direct concatenation of user input into Firestore references or queries. Always validate and sanitize data before it reaches Firestore operations.
1. Validate and normalize document paths
Use a validation layer to ensure IDs conform to expected patterns and avoid path traversal or namespace pollution.
// Safe: validate tenantId and configKey before use
const validator = use('Validator');
const payload = await validator.validateAll(req.all(), {
tenantId: 'required|alpha_dash|max:30',
configKey: 'required|alpha_dash|max:50'
});
if (payload.fails()) {
return response.badRequest(payload.messages());
}
const docRef = db.collection('tenants').doc(payload.tenantId).collection('configs').doc(payload.configKey);
const snapshot = await docRef.get();
if (!snapshot.exists) {
return response.notFound();
}
2. Use allowlisted fields and operators for dynamic queries
Do not trust user input for field names or operators. Map them to a strict allowlist before constructing queries.
// Safe: allowlist field and operator
const allowedFields = ['status', 'createdAt', 'priority'];
const allowedOps = ['==', '!=', '>', '>=', '<', '<='];
const field = req.input('field');
const operator = req.input('op');
const value = req.input('value');
if (!allowedFields.includes(field) || !allowedOps.includes(operator)) {
return response.badRequest('Invalid field or operator');
}
// Map operator strings to Firestore methods if needed; here using native Firestore operators
const query = db.collection('events').where(field, operator, value);
const snapshot = await query.get();
3. Sanitize stored AI and user content
When persisting prompts, model outputs, or user messages, enforce output encoding and schema validation to prevent injection of executable content or PII into Firestore documents.
// Safe: sanitize before storing AI output
const sanitize = (str) => {
return str.replace(/<[^>]+>/g, '').substring(0, 5000); // basic strip tags and length cap
};
const safeOutput = sanitize(generatedText);
await db.collection('interactions').add({
prompt: safeInput,
response: safeOutput,
createdAt: new Date()
});
4. Enforce authorization on document references
Always confirm that the requesting user has rights to the specific document, especially when IDs are predictable or derived from user input. Combine Firestore rules with application-level checks.
// Example application-level check before read
const docRef = db.collection('tenants').doc(tenantId).collection('members').doc(userId);
const membership = await docRef.get();
if (!membership.exists || membership.data().role !== 'admin') {
return response.forbidden();
}