Cache Poisoning in Express with Cockroachdb
Cache Poisoning in Express with Cockroachdb — how this specific combination creates or exposes the vulnerability
Cache poisoning in an Express service that uses CockroachDB occurs when an attacker causes cached responses to store attacker-controlled data or to be shared across users. Because CockroachDB is a distributed SQL database often used to store user-specific or tenant-specific data, an Express layer that caches query results without sufficient normalization can inadvertently serve one user’s data to another or expose sensitive fields.
The risk is introduced when the application builds cache keys from request parameters or headers without validating or normalizing them. For example, if an Express route caches responses using a URL or query parameter directly as part of the key, an attacker can vary parameters such as database schema, tenant identifier, or fields to include/exclude, causing cache entries to be shared in unsafe ways. CockroachDB’s SQL interface and support for complex queries (joins, window functions, tenant schemas) means that an Express route might generate dynamic SQL or ORM queries whose result sets differ by user context. If caching is applied before context checks or row-level security (RLS) enforcement, poisoned cache entries may bypass intended isolation mechanisms.
A concrete scenario: an Express route uses a query parameter fields to select columns and caches the JSON response keyed by the full query string. An authenticated user requests /api/profile?fields=id,name and the response is cached. Later, an unauthenticated or lower-privileged attacker requests /api/profile?fields=id,name,ssn, and due to a weak cache key, the cached response containing sensitive fields is returned to the attacker. Even if CockroachDB enforces RLS at the database level, the cache has already exposed data the application should not have served. This pattern is common when integrating frontend-driven field selection or GraphQL-like flexibility on top of CockroachDB without normalizing cache keys and validating authorization before caching.
Another vector involves caching responses that include database metadata or error details that can aid further attacks. CockroachDB error messages may include schema or table names; if such responses are cached and later served to other users, they leak reconnaissance information. Additionally, if the Express app uses shared caches (e.g., Redis or in-memory stores) without proper namespace isolation, tenant identifiers or user IDs embedded in cache keys or values can be confused across users, effectively achieving IDOR via cache poisoning.
Because middleBrick tests unauthenticated attack surfaces and checks for input validation and data exposure, it can surface cache poisoning risks by detecting endpoints that cache sensitive or user-specific responses without proper authorization checks. The scanner evaluates whether input validation and property authorization are applied before caching and whether findings map to frameworks such as OWASP API Top 10 and GDPR.
Cockroachdb-Specific Remediation in Express — concrete code fixes
To remediate cache poisoning when Express interfaces with CockroachDB, normalize cache keys, enforce authorization and validation before caching, and avoid caching sensitive or user-specific responses. Below are concrete patterns and code examples.
1. Keyed cache with user and field normalization
Ensure the cache key includes a user or tenant identifier and a canonical representation of allowed fields. Do not directly use raw query parameters in the cache key.
const crypto = require('crypto');
function buildCacheKey(userId, requestedFields) {
const allowedFields = new Set(['id', 'name', 'email']); // server-defined allowlist
const normalized = requestedFields
.split(',')
.map(f => f.trim())
.filter(f => allowedFields.has(f))
.sort()
.join(',');
const digest = crypto.createHash('sha256').update(`${userId}:${normalized}`).digest('hex');
return `user:${userId}:profile:fields:${digest}`;
}
// Express route example
app.get('/api/profile', async (req, res) => {
const userId = req.user?.id; // assume auth middleware set req.user
if (!userId) { return res.status(401).send('Unauthorized'); }
const fields = req.query.fields || 'id,name,email';
const key = buildCacheKey(userId, fields);
const cached = await cache.get(key);
if (cached) { return res.json(JSON.parse(cached)); }
const client = await pool.connect();
try {
// Use parameterized queries; CockroachDB supports $1, $2 style
const cols = fields.split(',').map((f, i) => `