Timing Attack in Feathersjs with Cockroachdb
Timing Attack in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
A timing attack in a Feathersjs service using Cockroachdb can occur when authentication or data-retrieval logic performs branching based on whether a record exists or whether a supplied identifier matches an owned record. In Feathersjs, a typical find or get handler may first query Cockroachdb for a row, then conditionally proceed based on the presence of that row. If the response time differs depending on whether the record exists or whether the record belongs to the requesting user, an attacker can infer information by measuring round-trip times.
For example, consider a Feathersjs service defined with an authentication layer that loads the user row from Cockroachdb by ID. If the handler uses a conditional that first checks whether a user record exists and then validates ownership, the time taken to hash a password or to perform the lookup can vary subtly based on whether the row is present. When combined with Cockroachdb, which is compatible with PostgreSQL protocol, differences in query plans, index usage, or network latency to the distributed nodes can amplify timing differences. An attacker who can send crafted requests and observe response times may infer whether specific user identifiers exist in the Cockroachdb cluster, or whether a given resource ID is associated with the authenticated subject.
Concrete attack patterns include probing numeric or UUID identifiers sequentially and measuring how long each request takes. If the service performs an indexed lookup on a primary key and then branches on whether the row belongs to the requester, the timing delta between a missing row and an unauthorized row can be measurable, especially under load or across regions. This becomes more pronounced if additional operations such as hashing or token verification are performed only in one branch. In Feathersjs, hooks that modify context or perform early returns can inadvertently introduce these timing differences. Because Cockroachdb implements serializable isolation and distributed transactions, certain query paths may exhibit variable latencies depending on leaseholder location or contention, further contributing to observable timing variance.
To illustrate, a vulnerable Feathersjs hook might look like the following simplified pattern, where the timing of the database query and the conditional check can leak information:
app.service('users').hooks({
before: {
async get(context) {
const { id } = context.params.query;
const row = await context.app.get('cockroachdb').query('SELECT * FROM users WHERE id = $1', [id]);
if (!row) {
// Timing difference when row is absent
return Promise.reject(new Error('Not found'));
}
if (row.userId !== context.params.user.id) {
// Timing difference when ownership differs
throw new Error('Forbidden');
}
context.result = row;
}
}
});
In this pattern, the time to execute the SELECT and the subsequent conditional checks can vary. An attacker who can perform network-level timing measurements and control the supplied ID may deduce whether a given user ID exists and whether it maps to accessible resources. Because the query is executed against Cockroachdb without constant-time safeguards, the branch behavior is exposed. Mitigations must ensure that execution paths and response behaviors remain consistent regardless of existence or ownership, and that database interactions do not introduce measurable timing differences that can be correlated with sensitive conditions.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on making data-access patterns independent of existence and ownership outcomes. In Feathersjs, this can be achieved by ensuring that queries always take a constant amount of time and that ownership checks do not introduce early branches that affect timing. Use a single query that joins the target record with the user context and returns a uniform result shape, avoiding conditional early exits based on presence or ownership.
Below is a secure Feathersjs hook that performs a constant-time lookup against Cockroachdb. The query retrieves the row only when it matches both the identifier and the user ownership; otherwise, it returns a deterministic empty result. This ensures that the response path and timing remain consistent regardless of whether the record exists or is owned by another user:
app.service('users').hooks({
before: {
async get(context) {
const { id } = context.params.query;
const userId = context.params.user && context.params.user.id;
// Constant-time query: always returns a row or empty result
const rows = await context.app.get('cockroachdb').query(
`SELECT data FROM users WHERE id = $1 AND user_id = $2`,
[id, userId]
);
// Uniform handling: no early throw based on existence or ownership
context.result = rows.length === 1 ? rows[0] : null;
}
}
});
If you need to differentiate between "not found" and "forbidden" at a higher layer without leaking timing, perform the distinction after a constant-time fetch by returning a generic error and logging internally for audit purposes, rather than branching on database-level conditions that affect response duration. The above query uses an indexed lookup on (id, user_id), which should be supported by an index on Cockroachdb to keep latency predictable:
-- Recommended index on Cockroachdb for consistent performance
CREATE INDEX idx_users_id_user_id ON users (id, user_id);
For operations that involve updates or deletions, apply the same principle by always executing the statement and inspecting the number of affected rows, rather than branching on existence checks beforehand:
app.service('messages').hooks({
before: {
remove(context) {
const { id } = context.id;
const userId = context.params.user && context.params.user.id;
// Execute without early branching
const result = context.app.get('cockroachdb').query(
'DELETE FROM messages WHERE id = $1 AND user_id = $2',
[id, userId]
);
// Uniform handling regardless of whether rows existed
context.isHandled = true;
return context;
}
}
});
Additionally, consider using parameterized queries and prepared statements where supported by your Cockroachdb driver to reduce variability in parsing and planning time. In Feathersjs, ensure that hooks do not perform asynchronous operations that depend on secret-dependent branches. By aligning your data-access patterns with constant-time principles and leveraging Cockroachdb’s indexing and transactional semantics, you can mitigate timing-based inference attacks while preserving the intended authorization semantics.