Double Free in Feathersjs with Cockroachdb
Double Free in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
Double Free is a memory safety class issue that can manifest in application logic rather than in the runtime or database engine itself. In a Feathersjs service that uses Cockroachdb, the pattern typically involves multiple asynchronous operations on the same logical resource without proper state reconciliation. For example, a client may send two concurrent requests to create or patch a record, and if Feathersjs does not enforce single-execution guards or idempotency, the service may issue two overlapping SQL transactions to Cockroachdb. Cockroachdb correctly serializes transactions and maintains ACID guarantees, but the application layer may interpret the two successful commits as two distinct side effects, leading to double bookkeeping, duplicate external calls, or double writes to related aggregates. This is not a Cockroachdb bug; it is a design and concurrency control issue in the Feathersjs service logic.
Consider a Featherjs service defined with feathers and @feathersjs/sequelize (or a custom transport) where the service method manually opens a Cockroachdb transaction, performs checks, and commits without locking or optimistic concurrency control:
const { Sequelize, DataTypes, Transaction } = require('sequelize');
const sequelize = new Sequelize('cockroachdb://...', {
dialect: 'postgres',
protocol: 'postgres',
port: 26257,
ssl: { rejectUnauthorized: false }
});
class SafeBookingService {
constructor() {
this.Booking = sequelize.define('Booking', {
id: { type: DataTypes.STRING, primaryKey: true },
seats: { type: DataTypes.INTEGER, allowNull: false }
});
}
async create(data) {
const transaction = await sequelize.transaction();
try {
const existing = await this.Booking.findOne({
where: { id: data.id },
transaction
});
if (existing) {
await transaction.rollback();
return { error: 'Already exists' };
}
await this.Booking.create({ id: data.id, seats: data.seats }, { transaction });
await transaction.commit();
return { success: true };
} catch (error) {
await transaction.rollback();
throw error;
}
}
}
If two requests arrive at nearly the same time, both may pass the findOne check before either commits, resulting in two create attempts. Cockroachdb will allow one to commit first and cause the second to violate a unique constraint (if enforced), but if the uniqueness guard is missing or handled poorly, you can end up with duplicated application-level state transitions — effectively a Double Free in terms of business effects. Feathersjs does not inherently provide distributed locking; developers must implement idempotency keys, optimistic locking via version columns, or explicit locks to prevent this class of issue.
The LLM/AI Security checks in middleBrick do not specifically flag Double Free, but they do detect anomalies in endpoint behavior such as inconsistent inventory states or unexpected repeated actions, which can indicate logic flaws like this. The scanner’s Inventory Management and Property Authorization checks can highlight endpoints where state changes are not properly reconciled across requests.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
To remediate Double Free-like issues when using Cockroachdb with Feathersjs, focus on idempotency, uniqueness constraints, and transaction isolation. Cockroachdb supports serializable isolation, which is the strongest level and prevents write skews that can lead to double application-side effects when used correctly.
First, enforce uniqueness at the database level with a unique constraint on business keys, and use INSERT ... ON CONFLICT DO NOTHING (or equivalent) to make writes idempotent:
await sequelize.transaction(async (transaction) => {
await this.Booking.upsert({
id: data.id,
seats: data.seats
}, {
transaction,
conflictFields: ['id']
});
});
Second, use explicit application-level idempotency keys stored in Cockroachdb to deduplicate requests:
async handleRequest(idempotencyKey, payload) {
const tx = await sequelize.transaction({ isolationLevel: 'SERIALIZABLE' });
try {
const existingKey = await IdempotencyKey.findOne({
where: { key: idempotencyKey },
transaction: tx
});
if (existingKey) {
await tx.commit();
return { cached: true, result: existingKey.result };
}
const record = await this.Booking.create({
id: payload.id,
seats: payload.seats
}, { transaction: tx });
await IdempotencyKey.create({
key: idempotencyKey,
result: record.toJSON()
}, { transaction: tx });
await tx.commit();
return record;
} catch (error) {
await tx.rollback();
throw error;
}
}
Third, prefer optimistic locking with a version column to ensure updates are applied to the expected state:
await this.Booking.update(
{ seats: data.seats },
{
where: { id: data.id, version: data.expectedVersion },
transaction
}
);
By combining Cockroachdb’s serializable transactions, unique constraints, and application-level idempotency keys, you prevent the conditions that lead to double application logic execution. middleBrick’s Continuous Monitoring in the Pro plan can help detect recurring anomalies in API behavior that may indicate unresolved logic flaws across deployments.