Data Exposure in Feathersjs with Cockroachdb
Data Exposure in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
In a Feathersjs application connected to Cockroachdb, data exposure typically occurs when service queries return more fields than the client is authorized to see. Feathersjs services are often configured with a generic ModelService that maps directly to a database table. If field-level authorization is not explicitly implemented, a query such as app.service('users').find({ query: { $select: [] } }) may still return sensitive columns like password_hash or ssn because the ORM layer does not automatically strip them. Cockroachdb, like PostgreSQL, returns all columns present in the table unless the SQL explicitly limits the column set. Without a $select or runtime row-level policy, the wire protocol from Cockroachdb to Feathersjs can expose entire rows.
Another vector specific to this stack is misuse of Feathersjs hooks where the params object is mutated or merged across hooks. A common pattern is to add the current user’s roles in an authentication hook and then inadvertently pass those roles into a downstream hook that builds the Cockroachdb query without sanitizing the projection. If the query builder uses Model.find({ where: { organizationId } }) and does not explicitly define attributes, Sequelize (or TypeORM) may generate SELECT *, thereby returning sensitive fields such as api_key or internal flags to the client.
Additionally, pagination misconfiguration can lead to data exposure. Feathersjs defaults to returning all records when paginate: false is not set explicitly. With Cockroachdb, a request like /users?$limit=1000 could dump thousands of rows containing sensitive columns if the service does not enforce a reasonable max limit or projection. The combination of an open-ended query, missing $select, and Cockroachdb’s tabular schema means sensitive columns such as reset_token or internal_notes can be delivered to an unauthenticated or low-privilege client through an otherwise valid API call.
These risks are amplified when using dynamic $expand or nested relations in Feathersjs adapters that translate to Cockroachdb JOINs. If the join pulls in columns from related tables without explicit field selection, confidential data from linked rows can leak into the response. For example, a vitals service might JOIN to a patient_records table and unintentionally expose diagnosis codes unless the projection is controlled at the Feathersjs service level.
To detect this class of issue, middleBrick runs 12 security checks in parallel, including Property Authorization and Data Exposure scans. It compares the OpenAPI/Swagger spec definitions with runtime responses, highlighting mismatches where server-side field selection is missing. The scanner does not modify data; it reports findings with severity and remediation guidance, helping teams identify unintended exposure before an attacker does.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on explicit field selection and query constraints in Feathersjs services. Always define events and ModelService hooks that sanitize the params object before it reaches the database layer. Below are concrete, working examples for Feathersjs with Cockroachdb using Sequelize.
1. Enforce $select via hooks
Use a before hook to restrict fields based on roles. This ensures that even if the client requests all fields, the server only returns what is permitted.
// src/hooks/restrict-fields.js
module.exports = function restrictFields(allowedFields) {
return function(context) {
const { params, result } = context;
if (params.user && params.user.roles.includes('admin')) {
// Admins see all fields
return context;
}
// For non-admin, explicitly limit fields at the query level
if (!context.params.query.$select) {
context.params.query.$select = allowedFields;
}
// Optionally strip sensitive fields from result if not filtered at DB level
if (Array.isArray(result.data)) {
result.data = result.data.map(row => {
const filtered = {};
allowedFields.forEach(field => { if (row[field] !== undefined) filtered[field] = row[field]; });
return filtered;
});
}
return context;
};
};
// src/services/user/user.hooks.js
const restrictFields = require('./restrict-fields');
module.exports = {
before: {
all: [],
find: [restrictFields(['id', 'email', 'name'])],
get: [restrictFields(['id', 'email', 'name'])]
},
after: {},
error: {}
};
2. Explicit attributes in Sequelize models
Define the model with explicit attributes and avoid sequelize.define defaults that map to all columns. In Feathersjs, configure the Sequelize service options to limit columns by default.
// src/services/user/user.model.js
const { Sequelize, DataTypes } = require('@feathersjs/sequelize');
const sequelize = new Sequelize('postgres://user:pass@cockroachdb:26257/dbname?sslmode=require');
const User = sequelize.define('user', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
email: { type: DataTypes.STRING, allowNull: false },
name: { type: DataTypes.STRING },
// Omit password_hash and ssn from default attributes
}, {
timestamps: true,
// Explicitly exclude sensitive columns from SELECT *
defaultScope: {
attributes: { exclude: ['password_hash', 'ssn', 'api_key'] }
}
});
module.exports = User;
3. Pagination and max limit guardrails
Prevent mass data dumps by configuring pagination with a strict max limit and ensuring $select is applied before the Cockroachdb query is built.
// src/services/user/user.service.js
const { Service } = require('feathers-sequelize');
class SecureUserService extends Service {
setup(app, path) {
super.setup(app, path);
this.hooks.push({
before: {
async find(context) {
const query = context.params.query || {};
// Enforce a reasonable max limit to avoid dumping large result sets
const maxLimit = 100;
if (!query.$limit || query.$limit > maxLimit) {
query.$limit = maxLimit;
}
// Ensure projection is defined to limit returned columns
if (!query.$select) {
query.$select = ['id', 'email', 'name'];
}
context.params.query = query;
return context;
}
}
});
}
}
module.exports = function () {
const app = this;
app.use('/users', new SecureUserService({
Model: require('./user.model'),
paginate: { default: 10, max: 100 }
}));
};
4. Secure JOINs and relations
When using relations or nested services, explicitly declare which fields to pull from related tables to avoid leaking columns from joined Cockroachdb tables.
// src/services/appointments/appointments.model.js
const Appointment = sequelize.define('appointment', {
id: { type: DataTypes.INTEGER, primaryKey: true },
patient_id: { type: DataTypes.INTEGER },
doctor_id: { type: DataTypes.INTEGER },
visit_notes: { type: DataTypes.TEXT }
});
// Explicitly include only safe fields from related Patient
Appointment.associate = (models) => {
Appointment.belongsTo(models.user, {
foreignKey: 'patient_id',
as: 'patient',
attributes: ['id', 'email', 'name'] // limit to safe fields
});
};
By combining these patterns—field projection in hooks, model scoping, pagination guards, and controlled relations—you reduce the risk of data exposure when Feathersjs interacts with Cockroachdb. middleBrick’s Data Exposure and Property Authorization checks validate these safeguards by correlating spec-defined expectations with runtime responses, providing severity-ranked guidance without claiming to remediate automatically.
middleBrick integrates into your workflow via the CLI (middlebrick scan <url>), the Web Dashboard for tracking scores over time, and the GitHub Action to fail builds if risk thresholds are exceeded. The MCP Server also allows scanning APIs directly from AI coding assistants, helping teams catch misconfigurations early.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |