Integrity Failures in Feathersjs with Cockroachdb
Integrity Failures in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for creating REST and real-time APIs with minimal configuration. When paired with CockroachDB, an open-source, cloud-native distributed SQL database, developers often assume strong consistency and transactional guarantees eliminate integrity issues. This is not always sufficient to prevent integrity failures if application-level validation and authorization are incomplete.
An integrity failure occurs when unauthorized data modification occurs, or when business rules that should protect data relationships are bypassed. With FeathersJS, this commonly maps to BOLA (Broken Level Authorization) and IDOR vulnerabilities. A service may correctly perform CRUD operations, but if the record ownership check is missing or misaligned with CockroachDB’s transaction boundaries, one user can read or alter another user’s rows.
CockroachDB’s serializable isolation helps avoid write skew at the database level, but it does not enforce application policies. For example, consider a FeathersJS service for user documents. If a hook does not scope queries by the authenticated user’s tenant or ID, an attacker can iterate through numeric IDs or manipulate query parameters to access rows they should not see. This becomes an integrity failure because the data’s confidentiality and correctness boundaries are violated, even though CockroachDB commits each write atomically.
Real-world patterns that expose integrity failures include missing or inconsistent use of the before hook to inject tenant context, or using the authenticated user’s ID directly without verifying it against the resource’s owning user. For instance, a find query like app.service('documents').find({ query: { userId: externalId } }) might appear safe, but if externalId is supplied by the client and not validated against the authenticated context, it can be tampered with to access other users’ data.
Additionally, FeathersJS hooks that perform multiple service calls or batch operations can introduce integrity issues when each step does not re-validate permissions under CockroachDB’s transaction. Without an explicit transaction that enforces all-or-nothing semantics at the application layer, partial updates may be applied, leaving the dataset in an inconsistent state. This is especially relevant when using after hooks that trigger side effects, such as notifications, without confirming that the originating write was fully authorized.
Serialization and deserialization further complicate integrity. If FeathersJS services accept payloads that reference related records by client-supplied identifiers, and those identifiers are not verified against CockroachDB in the same transactional context, attackers can substitute references to other valid records. For example, a PATCH request that includes { "groupId": 123 } should be validated to ensure the authenticated user has access to group 123, not merely that the field is present.
To detect these issues, middleBrick scans such API surfaces by correlating OpenAPI definitions with runtime behavior. It checks whether query parameters and payload fields are properly constrained by authentication context and whether service hooks enforce scoping consistently. This helps surface integrity weaknesses that are not obvious when inspecting code in isolation, especially when distributed SQL databases like CockroachDB handle consistency at a lower level than the application expects.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on ensuring every data access is scoped to the authenticated subject and validated within a consistent authorization boundary. Below are concrete FeathersJS hook and service examples that mitigate integrity failures when using CockroachDB.
1. Scoped find with authenticated user ID
Ensure that queries always include the authenticated user’s identifier and that the identifier is derived from the request context, not from client input.
const { authentication } = require('@feathersjs/authentication');
module.exports = {
before: {
all: [authentication()],
find: [context => {
// context.params.user contains the authenticated user from JWT/strategy
if (context.params.user) {
context.params.query.userId = context.params.user.id;
// Ensure client cannot override the userId filter
delete context.params.query.$or;
}
return context;
}]
}
};
2. Transactional write with ownership verification
When updating related records, use a transaction to preserve integrity. Re-verify ownership inside the transaction to avoid relying on cached assumptions.
const client = require('cockroachdb')('postgresql://user:pass@localhost:26257/db');
app.service('documents').hooks({
before: {
update: [async context => {
const { id, userId } = context.id;
const tx = client.transaction();
try {
const row = await tx.queryOne('SELECT id FROM documents WHERE id = $1 AND user_id = $2 FOR UPDATE', [id, userId]);
if (!row) {
throw new Error('Document not found or access denied');
}
// Proceed with update within the same transaction
context.params.tx = tx;
return context;
} catch (error) {
await tx.rollback();
throw error;
}
}],
create: [async context => {
const { userId } = context.params.user;
context.data.userId = userId;
// No client-supplied userId should be trusted
return context;
}]
},
after: {
update: [async context => {
if (context.params.tx) {
await context.params.tx.commit();
}
return context;
}]
}
});
3. Authorization check for related resources
When a payload references another entity (e.g., groupId), validate that relationship in the same logical unit of work rather than relying on client-provided values.
app.service('documents').hooks({
before: {
create: [async context => {
const { groupId } = context.data;
const { user } = context.params;
// Verify user’s membership in the group within the same permission check
const membership = await app.service('memberships').find({
query: {
userId: user.id,
groupId: groupId,
$select: ['id']
}
});
if (!membership.data.length) {
throw new Error('User not authorized for this group');
}
return context;
}]
}
});
4. Consistent scoping across services
Use a common hook to inject tenant or user scope so that all services apply the same filter. This prevents accidental cross-resource access when chaining service calls.
const scopeUserQuery = context => {
if (context.params.user && context.params.scoped !== false) {
context.params.query = context.params.query || {};
if (!context.params.query.userId) {
context.params.query.userId = context.params.user.id;
}
}
return context;
};
module.exports = {
before: {
all: [scopeUserQuery]
}
};
5. Input validation and type constraints
Even with scoping, validate identifiers to ensure they match expected types and formats, preventing type confusion or injection-style manipulations that could bypass intended filters.
const { iff, isMongoId } = require('feathers-hooks-common');
app.hooks({
before: {
find: [iff(
context => context.id === null,
validate({
query: {
userId: { type: 'string', isMongoId },
limit: { type: 'number', min: 1, max: 100 }
}
})
)]
}
});