Rate Limiting Bypass in Feathersjs with Cockroachdb
Rate Limiting Bypass in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
Feathersjs is a framework for real-time APIs that typically relies on services and hooks to enforce business rules. When rate limiting is implemented only at the Feathersjs layer without coordination with Cockroachdb, an attacker can exploit timing windows, distributed transaction behavior, or misconfigured hooks to bypass intended limits.
Cockroachdb, a distributed SQL database, provides strong consistency for reads and writes within a transaction, but it does not enforce application-level rate limits. If Feathersjs performs multiple sequential or conditional queries (e.g., read current count, then write), an attacker can interleave requests to exploit race conditions. For example, two concurrent requests may both read a count of 49, each pass the application check, and then both increment to 50, effectively allowing 100 operations within the window.
Additionally, Feathersjs hooks that run before or after a service method can be bypassed if the client targets lower-level routes or uses direct database access patterns that skip the service layer. Cockroachdb’s SQL interface does not inherently know about these application-level limits, so unless rate limiting is enforced as part of the query logic (e.g., via upsert and conditional increment within a single statement), the limit can be circumvented.
Another vector involves timestamp precision and clock skew. Feathersjs may use in-memory or cache-based sliding windows with millisecond timestamps, while Cockroachdb stores timestamps with higher precision. An attacker can send requests at the boundary of a window, relying on slight misalignment between the app cache and database commit timestamps to exceed the limit without detection.
Finally, if error handling in Feathersjs services does not properly validate database errors from Cockroachdb (such as transaction retries due to contention), an attacker can force repeated retries that consume allowed request slots without incrementing the effective count, effectively bypassing the intended throttle.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
To securely enforce rate limits with Cockroachdb in Feathersjs, perform the increment and check within a single SQL statement, avoiding read-then-write patterns. Use conditional upserts and explicit transactions to ensure atomicity. Below are concrete examples demonstrating a safe approach.
First, ensure your service hook uses a database-centric rate limiter that executes entirely within Cockroachdb. The following snippet shows a before hook that atomically increments a counter and enforces the limit.
const { knex } = require('feathers-knex');
const db = knex({
client: 'pg',
connection: process.env.COCKROACHDB_URI
});
app.hooks({
before: {
async create(context) {
const { userId, windowMs = 60000, maxRequests = 100 } = context.params.query;
const windowStart = new Date(Date.now() - windowMs);
const tableName = 'rate_limits';
// Ensure the table exists (run once):
// CREATE TABLE IF NOT EXISTS rate_limits (user_id STRING NOT NULL, ts TIMESTAMPTZ NOT NULL);
const result = await db.transaction(async trx => {
// Remove old entries outside the sliding window
await trx(tableName).where('user_id', userId).andWhere('ts', '<', windowStart).del();
// Count current requests in window
const [{ count }] = await trx(tableName).where('user_id', userId).count({ count: 'rowId' }).first();
if (count >= maxRequests) {
throw new Error('Rate limit exceeded');
}
// Insert current request atomically within the same transaction
await trx(tableName).insert({ user_id: userId, ts: new Date() });
return { remaining: maxRequests - count - 1 };
});
context.params.rateLimit = result;
return context;
}
}
});
This approach uses a single transaction to delete outdated rows, count current rows, and insert the new row, which Cockroachdb handles with serializable isolation. By keeping all operations server-side, you avoid race conditions that would occur if the count were checked in Feathersjs memory or cache before writing to the database.
Second, for higher throughput, consider a token-bucket implementation stored in Cockroachdb with conditional upserts. The following example uses an upsert with a computed remaining token count, ensuring the update only succeeds if sufficient tokens are available.
const { knex } = require('feathers-knestep');
const db = knex({
client: 'pg',
connection: process.env.COCKROACHDB_URI
});
async function checkRateLimit(userId, refillRate, capacity) {
const now = new Date();
const result = await db('rate_buckets')
.where('user_id', userId)
.transacting(knex.transaction())
.then(async trx => {
const row = await trx.select('tokens', 'last_refill').from('rate_buckets').where('user_id', userId).forUpdate().first();
let tokens = row ? row.tokens : capacity;
const lastRefill = row ? new Date(row.last_refill) : now;
const elapsed = now - lastRefill;
const refill = (elapsed / 1000) * refillRate;
tokens = Math.min(capacity, tokens + refill);
if (tokens < 1) {
throw new Error('Rate limit exceeded');
}
tokens -= 1;
await trx('rate_buckets').upsert({ user_id: userId, tokens, last_refill: now });
return { tokens: Math.floor(tokens), capacity };
});
return result;
}
// In a Feathersjs before hook:
app.hooks({
before: {
async create(context) {
const { userId } = context.params.query;
const limit = await checkRateLimit(userId, 10, 100); // 10 tokens per second, burst 100
context.params.rateLimit = limit;
return context;
}
}
});
These examples assume a table rate_limits or rate_buckets with appropriate indexes on user_id and ts. They also assume the Feathersjs service is configured to use Knex with Cockroachdb as the client. By pushing the logic into the database transaction, you mitigate timing-based bypasses that occur when checks and updates are split across application and database layers.
Finally, combine this with Feathersjs service methods that avoid custom query parameters that could skip hooks. Ensure all access routes go through the standard service interface so that the before hook always runs. For deployments, use the middleBrick CLI to scan your Feathersjs endpoints and validate that rate limiting findings are resolved, integrating scans into your CI/CD pipeline with the GitHub Action to fail builds if risk scores degrade.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |