Cache Poisoning in Express with Dynamodb
Cache Poisoning in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
Cache poisoning in an Express service that uses DynamoDB typically occurs when user-controlled or untrusted data is used to construct cache keys, query parameters, or response values that are later stored or reused. Because DynamoDB is often used as a persistent cache layer or a source for reference data, an attacker may manipulate inputs so that malicious content is written into cache entries or returned to other users.
Consider an Express route that caches user profile responses in DynamoDB with a key derived from an unvalidated query parameter, such as userId. If the application does not strictly validate or sanitize the key, an attacker can supply a crafted value that causes cache entries to be written under shared or predictable keys. Subsequent requests for those keys retrieve the poisoned data, leading to unauthorized information disclosure or incorrect behavior.
DynamoDB itself does not introduce caching logic, but its role as a storage backend means that if the Express layer stores attacker-influenced responses, the poisoned entries persist across requests and users. This is distinct from in-memory cache poisoning and can affect multiple clients over longer periods. Common triggers include missing input validation on route parameters, improper use of user-supplied values in conditional expressions, and unchecked usage of DynamoDB query filters that are directly derived from request content.
In the context of security scans, such patterns are flagged under BFLA/Privilege Escalation, Property Authorization, and Input Validation checks. The scan compares runtime behavior against the OpenAPI specification and detects whether unauthenticated endpoints can write unexpected or sensitive data into responses that may be reused. This is especially important when the API exposes DynamoDB-backed endpoints without authentication, as unauthenticated LLM endpoints and other unchecked surfaces increase the risk of manipulation.
Example of a vulnerable Express route that uses DynamoDB to cache responses based on an unvalidated country parameter:
app.get('/profile', async (req, res) => {
const country = req.query.country;
const cacheKey = `profile:${country}`;
const cached = await dynamodb.get({ TableName: 'CacheTable', Key: { id: cacheKey } }).promise();
if (cached.Item) {
return res.json(cached.Item);
}
const data = await fetchProfileData(country);
await dynamodb.put({ TableName: 'CacheTable', Item: { id: cacheKey, body: data } }).promise();
res.json(data);
});
In this example, if country is not validated, an attacker can supply values like __proto__ or path traversal strings to overwrite unrelated cache entries. The poisoned cache then serves malicious or incorrect data to other users, illustrating how improper key construction in Express combined with DynamoDB storage enables persistent cache poisoning.
Dynamodb-Specific Remediation in Express — concrete code fixes
To prevent cache poisoning when using DynamoDB with Express, enforce strict input validation, avoid direct use of user input in cache keys, and ensure separation between user contexts. Use a deterministic, sanitized key scheme and validate all query parameters against an allowlist before using them in DynamoDB operations.
Below is a hardened version of the previous route. It validates country against an allowlist, normalizes the key, and avoids storing raw user input directly. It also uses conditional writes to reduce accidental overwrites.
const allowedCountries = ['us', 'gb', 'de', 'fr', 'jp'];
function isValidCountry(value) {
return typeof value === 'string' && allowedCountries.includes(value.toLowerCase());
}
app.get('/profile', async (req, res) => {
const country = req.query.country;
if (!isValidCountry(country)) {
return res.status(400).json({ error: 'Invalid country' });
}
const normalized = country.toLowerCase();
const cacheKey = `profile:v1:${normalized}`;
const cached = await dynamodb.get({ TableName: 'CacheTable', Key: { id: cacheKey } }).promise();
if (cached.Item) {
return res.json(cached.Item);
}
const data = await fetchProfileData(normalized);
await dynamodb.put({ TableName: 'CacheTable', Item: { id: cacheKey, body: data, version: 'v1' } }).promise();
res.json(data);
});
Additional remediation practices include: never using unfiltered user input in conditional expressions that affect DynamoDB query filters; applying output encoding for any data rendered in responses; and monitoring for unexpected cache key patterns using logging or anomaly detection. These measures reduce the attack surface for both runtime manipulation and stored cache poisoning.
When scanning with middleBrick, the CLI can be used to verify remediation by running middlebrick scan <url> against the updated endpoints. The dashboard helps track security scores over time, and the GitHub Action can enforce a minimum score threshold in CI/CD pipelines to prevent regressions. These integrations support continuous monitoring so that changes to input handling or caching strategies are evaluated before deployment.