Privilege Escalation in Feathersjs with Cockroachdb
Privilege Escalation in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
Privilege Escalation in a Feathersjs service backed by Cockroachdb commonly arises when authorization checks are performed only in the application layer or are inconsistent with database permissions. Feathersjs is a framework that encourages a service-oriented architecture where each service exposes methods such as find, get, create, update, and patch. If a service does not enforce scope-based data filtering and relies solely on user roles stored in the application, an attacker may manipulate requests to access or modify records belonging to other users.
When using Cockroachdb, which is PostgreSQL-compatible, privilege escalation can occur if the database user used by Feathersjs has broader privileges than necessary (for example, superuser or broad table access) and the application does not enforce row-level security (RLS) or implement proper filters in service hooks. An attacker could exploit IDOR (Insecure Direct Object References) by iterating through numeric or UUID identifiers and leveraging weak or missing ownership checks to read or update other users’ data. For instance, a before hook that sets params.query.userId based on authentication can be bypassed if the developer forgets to apply the same filter in the database query or if the Cockroachdb user has unrestricted SELECT access to the table.
Another specific risk with Cockroachdb in Feathersjs is the misuse of $or and complex query objects constructed from untrusted input. If a service method builds dynamic queries without strict validation, an attacker might inject conditions that expand the scope of accessible records. Because Cockroachdb implements SQL semantics similar to PostgreSQL, crafted query fragments can sometimes bypass intended filters when combined with improper serialization of parameters. Additionally, if the Feathersjs service uses the Feathers authentication hooks that attach the user object to params, but the service code does not consistently apply those filters across related methods (e.g., get versus custom methods), an elevation of privilege may occur where one user can impersonate another by altering identifiers.
Real-world attack patterns include enumeration of IDs in sequential order, exploitation of missing ownership checks in custom service methods, and abuse of administrative roles configured in the Cockroachdb connection string. For example, a service might expose a custom promote method that accepts an id parameter and updates a user’s role field without verifying that the requesting user has the right to perform such an action. If the underlying Cockroachdb user has write access to the users table and the service does not enforce scope isolation, the attacker can modify any row.
To detect such issues during a scan, middleBrick runs checks that analyze the unauthenticated attack surface and cross-reference OpenAPI specifications with runtime behavior. In the context of Feathersjs and Cockroachdb, this means validating that query parameters are constrained, that hooks properly scope data access, and that least-privilege database permissions are aligned with service logic. The scanner highlights findings related to BOLA/IDOR and privilege escalation and provides remediation guidance tied to frameworks such as OWASP API Top 10.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on enforcing ownership at the database query level, using parameterized queries, and applying strict hooks in Feathersjs. The following examples demonstrate how to implement scoped data access with Cockroachdb in a Feathersjs service.
- Define a custom hook that injects the authenticated user’s ID into the query and ensure it is applied to all relevant methods:
// src/hooks/user-scope.js
module.exports = function userScope(options = {}) {
return async context => {
const { user } = context.params;
if (user && user.id) {
// Apply scoping to queries for methods that retrieve or modify records
if (context.params.query) {
context.params.query.userId = user.id;
} else {
context.params.query = { userId: user.id };
}
}
return context;
};
};
- Apply the hook in your service and use a parameterized query that explicitly filters by userId. This ensures that even if an attacker manipulates the ID parameter, the database will only return rows matching the authenticated user:
// src/services/records/records.service.js
const { Service } = require('feathersjs');
const { SequelizeFeathersHooks } = require('feathers-sequelize-cockroachdb');
const userScope = require('./hooks/user-scope');
class RecordsService extends Service {
async find(params) {
const { user } = params;
// Enforce scope in the query object
const query = {
where: {
userId: user.id,
...(params.query && params.query.filter)
},
...params.query
};
return this.model.findAll(query);
}
async get(id, params) {
const { user } = params;
const row = await this.model.findOne({
where: {
id,
userId: user.id
}
});
if (!row) {
throw new Error('Not found');
}
return row;
}
}
module.exports = function setupService(app) {
const service = new RecordsService({
name: 'records',
model: require('./model'),
paginate: { default: 10, max: 50 }
});
app.use('/records', service);
service.hooks({
before: {
all: [userScope()],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
});
};
- When connecting Feathersjs to Cockroachdb, use a database user with minimal privileges. Do not use a superuser. Create a dedicated role with SELECT, INSERT, UPDATE, and DELETE only on the required tables, and apply Row Level Security policies if available. For example, in Cockroachdb, you can enforce RLS on a table and create policies that restrict rows by tenant or user:
-- Cockroachdb SQL setup
CREATE TABLE records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
data JSONB,
created_at TIMESTAMP DEFAULT now()
);
ALTER TABLE records ENABLE ROW LEVEL SECURITY;
CREATE POLICY records_user_policy ON records
USING (user_id = current_setting('app.user_id')::UUID);
-- Set the user context in your application connection string or init script
SET app.user_id = 'some-uuid';
- Validate and sanitize all inputs to prevent query manipulation. Use Joi or another validator in before hooks to ensure IDs and filters conform to expected formats:
const { validateQuery } = require('./validation');
app.service('records').hooks({
before: {
find: [context => {
const { query } = context.params;
const safeQuery = validateQuery(query);
context.params.query = safeQuery;
return context;
}]
}
});
By combining scoped hooks, parameterized queries with explicit WHERE clauses, minimal database privileges, and input validation, you reduce the risk of privilege escalation in Feathersjs applications backed by Cockroachdb. These practices align with checks that middleBrick performs, such as verifying that filters are applied consistently and that the API surface does not expose excessive agency or insecure direct object references.