Vulnerable Components in Feathersjs with Cockroachdb
Vulnerable Components in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for creating JavaScript APIs with services centered around CRUD operations. When FeathersJS applications connect to CockroachDB, a distributed SQL database, specific patterns in query construction and data handling can expose components to common API security risks such as IDOR, mass assignment, and injection. These vulnerabilities arise from a mismatch between FeathersJS service flexibility and CockroachDB schema constraints or from insufficient validation before queries are issued.
One vulnerable component is the dynamic query builder used by FeathersJS adapters. If a FeathersJS service for a users entity builds WHERE clauses by directly interpolating request query parameters into SQL-like conditions without strict allowlisting, an attacker can manipulate filters to access other users’ records. For example, a request like /users?email=attacker@example.com could be altered to /users?email=anyone@example.com&$or[0][id]=$eq&0 to bypass intended isolation when CockroachDB returns broader results due to permissive adapter configuration.
Another component is the lack of strict schema enforcement for nested objects. FeathersJS services often accept JSON payloads for create or patch operations. If these payloads are mapped directly to CockroachDB columns without validation, an attacker can inject unexpected fields such as isAdmin or role to escalate privileges. This becomes dangerous when CockroachDB columns like role are used in row-level security (RLS) policies; improperly validated input can bypass intended access controls.
Input validation gaps also manifest in the handling of array and JSONB fields. CockroachDB supports JSONB columns, and FeathersJS services that accept arbitrary JSON objects may forward unsanitized arrays or objects into queries. Without proper sanitization, this can lead to injection-like behavior or unexpected data exposure when combined with CockroachDB’s JSON operators. For instance, a malformed JSON payload could cause the service to retrieve or modify unintended rows if the adapter does not enforce type and structure checks.
Finally, insufficient rate limiting and unauthenticated endpoint exposure in the FeathersJS service layer can amplify the impact of vulnerable components. If a /users service is publicly accessible and lacks robust rate limits, an attacker can conduct enumeration attacks against CockroachDB to infer valid user IDs or exploit IDOR patterns at scale. These design-level interactions between FeathersJS service definitions and CockroachDB access patterns create a chain that can lead to data exposure or privilege escalation.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on strict validation, parameterized queries, and schema-aware data handling. Below are concrete code examples for a FeathersJS service connected to CockroachDB.
1. Use parameterized queries with explicit field allowlisting
Ensure that query parameters are never directly interpolated. Use FeathersJS hooks to sanitize and parameterize inputs before they reach the adapter.
// src/hooks/validate-user-query.js
module.exports = function validateUserQuery() {
return function(context) {
const { email, $limit, $skip } = context.params.query;
const allowedFields = ['email', 'status'];
const filteredQuery = { $limit: Number($limit) || 10, $skip: Number($skip) || 0 };
if (email) {
if (!/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email)) {
throw new Error('Invalid email format');
}
filteredQuery.email = email;
}
// Explicitly allow only safe fields
Object.keys(context.params.query).forEach(key => {
if (allowedFields.includes(key)) {
filteredQuery[key] = context.params.query[key];
}
});
context.params.query = filteredQuery;
return context;
};
};
// In service definition (src/services/users/users.service.js)
const { Service } = require('feathersjs-sequelize');
const validateUserQuery = require('./hooks/validate-user-query');
class UserService extends Service {
async find(params) {
const query = params.query || {};
// Use parameterized conditions for CockroachDB
const where = {};
if (query.email) where.email = query.email;
const users = await this.Model.findAll({
where,
limit: Number(query.$limit) || 10,
offset: Number(query.$skip) || 0
});
return { total: users.length, data: users };
}
}
module.exports = function() {
const app = this;
app.use('/users', new UserService({
Model: app.get('knex'), // configured for CockroachDB
paginate: { default: 10, max: 50 }
}));
const userService = app.service('users');
userService.hooks({
before: {
all: [validateUserQuery()],
find: []
}
});
};
2. Strict schema validation for create/patch payloads
Define an allowlisted set of fields and types for each operation to prevent privilege escalation via unexpected fields.
// src/hooks/validate-payload.js
const allowedCreateFields = ['email', 'name', 'status'];
const allowedPatchFields = ['name', 'status'];
module.exports = function validatePayload() {
return function(context) {
const data = context.data || {};
const isPatch = context.method === 'patch';
const allowed = isPatch ? allowedPatchFields : allowedCreateFields;
const filtered = {};
Object.keys(data).forEach(key => {
if (allowed.includes(key)) {
if (key === 'email' && !/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(data[key])) {
throw new Error('Invalid email');
}
filtered[key] = data[key];
}
});
context.data = filtered;
return context;
};
};
// Usage in service
app.use('/users', new UserService({ /* options */ }));
app.service('users').hooks({
before: {
create: [validatePayload()],
patch: [validatePayload()]
}
});
3. Enforce row-level security via hooks
Use hooks to scope queries to the requesting user, ensuring that even if IDOR patterns exist, data access is limited.
// src/hooks/user-scope.js
module.exports = function userScope() {
return function(context) {
if (context.params.user && context.params.user.id) {
context.params.query = context.params.query || {};
context.params.query.userId = context.params.user.id;
}
return context;
};
};
// Ensure your CockroachDB table has a userId column and RLS policy
// Example RLS policy (run in CockroachDB):
// CREATE POLICY "users_own_data" ON users FOR ALL
// USING (auth_role() = 'authenticated' AND user_id = current_setting('app.user_id')::INT);
4. Sanitize JSONB and array inputs
Validate the structure of JSONB inputs to prevent unexpected queries or execution paths.
// src/hooks/validate-json-body.js
module.exports = function validateJsonBody() {
return function(context) {
const data = context.data || {};
if (data.metadata && typeof data.metadata === 'object') {
// Ensure metadata keys are alphanumeric
Object.keys(data.metadata).forEach(key => {
if (!/^[a-zA-Z0-9_-]+$/.test(key)) {
throw new Error('Invalid metadata key');
}
});
}
context.data = data;
return context;
};
};
5. Apply rate limiting and authentication checks
Use FeathersJS hooks to enforce rate limits and ensure endpoints are not unintentionally public.
// src/hooks/rate-limit.js
const rateLimitMap = new Map();
module.exports = function rateLimit() {
return function(context) {
const ip = context.params.ip || 'unknown';
const now = Date.now();
const window = 60000; // 1 minute
const maxRequests = 100;
if (!rateLimitMap.has(ip)) {
rateLimitMap.set(ip, []);
}
const timestamps = rateLimitMap.get(ip).filter(t => now - t < window);
if (timestamps.length >= maxRequests) {
throw new Error('Rate limit exceeded');
}
timestamps.push(now);
rateLimitMap.set(ip, timestamps);
return context;
};
};
// Apply to public services
app.service('public-data').hooks({
before: {
all: [rateLimit()]
}
});