Cache Poisoning in Restify with Cockroachdb
Cache Poisoning in Restify with Cockroachdb — how this specific combination creates or exposes the vulnerability
Cache poisoning in a Restify service backed by CockroachDB occurs when an attacker manipulates cache keys or cached responses so that malicious or incorrect data is served to subsequent users. Because Restify often uses request parameters, headers, or query strings as part of the caching decision, inconsistent normalization or unsafe inclusion of user-controlled values can cause the cache to store responses keyed by attacker-controlled inputs.
When the cached response includes data derived from CockroachDB—such as tenant identifiers, organization IDs, or row-level security predicates—poisoned cache entries may cause one user’s data to be returned to another user. This can bypass logical access controls that rely on cached query results, effectively exposing or altering data without direct database access. The interaction between Restify’s routing and middleware cache logic and CockroachDB’s distributed SQL execution means that a poisoned cache can persist across node restarts and spread through the cluster if the cache is shared.
For example, if a Restify endpoint uses a raw user-supplied accountId as part of the cache key without strict validation or canonicalization, an attacker can request /profile?accountId=..%2F.. or otherwise inject crafted values that map to another tenant’s CockroachDB rows. Subsequent requests for the same manipulated key will receive cached data belonging to the victim tenant, leading to unauthorized data exposure (a form of Insecure Direct Object Reference). This becomes more impactful when combined with missing or misconfigured authorization checks in the application layer, because the cache may serve data that would otherwise be rejected by database row-level policies.
Common root causes include missing input validation on query parameters used for cache keys, insufficient normalization of headers, and failure to scope cache entries by tenant or user context. In a distributed environment like CockroachDB, where strong consistency is provided but caching is applied at the API layer, it is critical to ensure that cache keys uniquely and safely represent the exact query and authorization context. Without this, poisoned cache entries can persist and be served across multiple nodes and sessions.
Cockroachdb-Specific Remediation in Restify — concrete code fixes
To remediate cache poisoning when using CockroachDB with Restify, enforce strict input validation, canonicalize cache keys, and scope entries by tenant and user context. Avoid directly embedding user input into cache keys; instead, map it to safe identifiers after verification.
Example: Safe parameterized endpoint with tenant scoping
const restify = require('restify');
const { Pool } = require('pg');
const server = restify.createServer();
const pool = new Pool({
connectionString: process.env.COCKROACHDB_URL,
});
server.get('/profile', async (req, res, next) => {
// 1) Validate and canonicalize input
const rawAccountId = req.query.accountId;
if (!/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test(rawAccountId)) {
return res.send(400, { error: 'Invalid account ID' });
}
const accountId = rawAccountId.toLowerCase();
// 2) Authorize: ensure the requesting user owns this accountId
const authAccount = req.headers['x-auth-account'];
if (authAccount !== accountId) {
return res.send(403, { error: 'Forbidden' });
}
// 3) Use parameterized CockroachDB queries with tenant-aware cache key
const query = 'SELECT id, name, settings FROM profiles WHERE account_id = $1';
const result = await pool.query(query, [accountId]);
if (result.rows.length === 0) {
return res.send(404, { error: 'Not found' });
}
// 4) Cache key includes tenant and query fingerprint, not raw user input
const cacheKey = `profile:tenant:${accountId}:version:1`;
// pseudo-cache set (implementation depends on your caching layer)
// cache.set(cacheKey, result.rows, { ttl: 60 });
res.send(200, result.rows[0]);
return next();
});
server.listen(8080, () => {
console.log('Server listening on port 8080');
});
Example: Parameterized CockroachDB query with prepared statement
const { Client } = require('pg');
async function getProfileSafe(accountId, authAccount) {
const client = new Client({
connectionString: process.env.COCKROACHDB_URL,
});
await client.connect();
// Use parameterized query to avoid SQL injection
const sql = 'SELECT id, display_name FROM accounts WHERE id = $1 AND tenant_id = $1';
const result = await client.query(sql, [accountId]);
await client.end();
return result.rows;
}
Best practices summary
- Validate and canonicalize all inputs before using them in cache keys or SQL queries.
- Scope cached responses by tenant and user context; avoid global keys based on raw parameters.
- Use parameterized queries with CockroachDB to prevent injection and ensure consistent plan caching.
- Apply consistent hashing or normalization for identifiers to prevent case-sensitive cache splits.
- Implement cache invalidation strategies that respect authorization boundaries and data ownership.