Injection Flaws in Feathersjs with Mongodb
Injection Flaws in Feathersjs with Mongodb — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework that encourages a service-based architecture. A Feathers service typically exposes CRUD operations and can plug multiple transports (REST, Socket.io). When paired with a MongoDB adapter, developers may inadvertently create injection flaws if they construct queries from unchecked inputs. Unlike SQL, NoSQL injection in MongoDB often involves manipulation of the query document structure itself.
Consider a service that filters events by a user-supplied category parameter. If the adapter merges parameters directly into the find query, an attacker can inject operators to alter logic. For example, a request like ?category[in]=general,confidential could be interpreted as a query operator if the input is not validated. This becomes critical when the service uses $where, $eval, or other server-side JavaScript features, which allow execution of arbitrary code on the database side.
Another vector is the use of aggregation pipelines. If a Feathers hook dynamically adds stages such as $match or $project using unchecked fields, an attacker may inject expressions that change data visibility or cause excessive resource consumption. For instance, injecting { $where: "this.password == \"admin\"" } into a pipeline could bypass intended filters.
IDOR (Insecure Direct Object References) often coexists with injection flaws. A service might accept an id parameter and embed it directly in a MongoDB query without verifying ownership. An attacker iterating through predictable ObjectIds could access other users’ data if the query does not enforce tenant or user scope.
LLM/AI Security checks in middleBrick detect cases where system prompts or instructions might be exposed via injected inputs that manipulate service behavior. For example, crafting a payload that changes the semantic meaning of a query could reveal internal instructions if the service logs or echoes parts of the injected document. middleBrick’s active prompt injection testing probes for such logic tampering.
Data Exposure and Encryption checks highlight risks when sensitive fields are returned due to injected conditions that bypass projection controls. If an attacker injects a $where clause that always evaluates to true, the service might return documents that should be hidden, increasing the impact of the flaw.
Mongodb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on strict input validation, avoiding dynamic query building, and leveraging the MongoDB driver’s built-in protections.
1. Validate and sanitize all inputs
Use a schema validator (e.g., Ajv) to enforce types and allowed values before constructing queries. Never directly merge request query or body into a MongoDB filter.
// src/hooks/validate-category.js
'use strict';
const Ajv = require('ajv');
const ajv = new Ajv();
const validate = ajv.compile({
type: 'object',
properties: {
category: { type: 'string', enum: ['general', 'news', 'events'] },
limit: { type: 'integer', minimum: 1, maximum: 50 }
},
additionalProperties: false
});
module.exports = function() {
return function(hook) {
const valid = validate(hook.params.query);
if (!valid) {
throw new Error('Invalid query parameters');
}
return Promise.resolve(hook);
};
};
2. Use parameterized queries and avoid $where
Prefer explicit field matching. If you must use operators, define a whitelist of allowed operators and map user input safely.
// src/services/events/events.class.js
const { Service } = require('feathers-mongodb');
class SafeEventsService extends Service {
async find(params) {
const { query = {} } = params;
const filter = {};
if (query.category) {
filter.category = query.category;
}
if (query.status) {
filter.status = query.status;
}
// Explicitly avoid constructing $where from user input
return this.model.find(filter).toArray();
}
}
module.exports = function(app) {
app.use('/events', new SafeEventsService({
Model: app.get('mongodb').collection('events'),
paginate: { default: 10, max: 50 }
}));
};
3. Secure aggregation pipelines
Do not allow user input to dictate stage structure. If dynamic stages are required, validate each stage key against a known set.
// src/hooks/build-pipeline.js
const ALLOWED_STAGES = new Set(['$match', '$project', '$sort', '$limit']);
function buildPipeline(userStages) {
if (!Array.isArray(userStages)) {
return [{ $match: {} }];
}
return userStages
.filter(stage => typeof stage === 'object' && stage !== null)
.map(stage => {
const key = Object.keys(stage)[0];
if (!ALLOWED_STAGES.has(key)) {
throw new Error('Invalid aggregation stage');
}
return stage;
});
}
module.exports = function(hook) {
if (hook.params.query && hook.params.query.pipeline) {
hook.params.pipeline = buildPipeline(hook.params.query.pipeline);
delete hook.params.query.pipeline;
}
return Promise.resolve(hook);
};
4. Enforce ownership in queries
Always scope queries by user ID in multi-tenant applications. Use hooks to inject the user context rather than trusting client-provided IDs.
// src/hooks/ownership-check.js
module.exports = function() {
return function(hook) {
if (hook.params.user) {
// Ensure every find operation includes user ownership
if (hook.params.query) {
hook.params.query.userId = hook.params.user._id;
} else {
hook.params.query = { userId: hook.params.user._id };
}
}
return Promise.resolve(hook);
};
};
5. Disable server-side execution features
Ensure MongoDB is configured without --eval or --javascript enabled. In the adapter configuration, avoid options that permit $where or function evaluation.
// In your Feathers MongoDB adapter setup
const mongodb = require('@feathersjs/transport-commons');
const { MongodbService } = require('feathers-mongodb');
app.use('/data', new MongodbService({
Model: app.get('mongodb').collection('data'),
// Disable any options that allow JavaScript execution
useEstimatedDocumentCount: true,
// Ensure no eval-like options are passed
}));