Cache Poisoning in Adonisjs with Dynamodb
Cache Poisoning in Adonisjs with Dynamodb — how this specific combination creates or exposes the vulnerability
Cache poisoning in the AdonisJS + DynamoDB context occurs when an attacker can influence cache keys or cached response data in a way that causes subsequent users to receive unintended or malicious responses. Because AdonisJS applications often use in-memory or external caches to avoid repeated DynamoDB requests, unsafe cache key construction or unchecked cache content can turn the cache into an attack surface.
DynamoDB does not execute code or interpret data, but the application logic that reads and writes items can be tricked into storing or retrieving attacker-controlled values. For example, if the cache key is derived directly from user-supplied input without normalization or strict validation, two requests that differ only in casing, encoding, or injected metadata may map to distinct logical entries. An attacker may cause a poisoned entry to overwrite a legitimate cached item, leading to data leakage or inconsistent behavior for other users.
In AdonisJS, common patterns that increase risk include:
- Using raw request parameters such as
req.params.idorreq.query.slugas part of the cache key without normalization. - Caching entire query responses that include user-specific fields (e.g., tenant identifiers) without scoping the key to the tenant.
- Storing DynamoDB query results or metadata in cache and failing to validate that the data shape matches expectations before reuse.
Consider an endpoint that caches user profile data with a key like profile:${userId}. If userId is taken directly from a URL parameter and not validated as a UUID or numeric ID, an attacker could request /profile/123%2F%2Fevil or otherwise manipulate the string, producing a separate cache entry that may be returned to other users under certain routing or collision conditions. DynamoDB will store and retrieve the item based on the exact key, so the poisoned cache entry persists across requests.
Another scenario involves multi-tenant applications where tenant context is not enforced in the cache key. An authenticated user in tenant A might request data that is cached under a key that does not include the tenant ID. If tenant B’s data is later stored under the same key, user A could temporarily see tenant B’s data from the cache. DynamoDB itself remains consistent, but the application’s caching strategy introduces the cross-tenant exposure.
Because middleBrick tests unauthenticated attack surfaces and includes input validation and data exposure checks, it can surface these cache poisoning risks by identifying weak cache-key construction and insufficient separation of cached responses across users or tenants.
Dynamodb-Specific Remediation in Adonisjs — concrete code fixes
To mitigate cache poisoning when using DynamoDB with AdonisJS, focus on deterministic cache keys, tenant-aware scoping, and strict validation of cached content. The following patterns assume you are using the AWS SDK for JavaScript v3 and a cache layer such as Redis or an in-memory store integrated into your AdonisJS provider.
1. Deterministic, normalized cache keys
Normalize inputs before constructing cache keys. Strip unnecessary whitespace, enforce expected formats, and hash complex inputs to produce stable keys.
import { sha256 } from '@aws-crypto/sha256-js';
function cacheKeyForProfile(inputId: string): string {
// Normalize: trim, lowercase, and ensure expected format
const normalized = inputId.trim().toLowerCase();
// Optionally enforce format, e.g., UUID or integer pattern
const stable = /^[a-f0-9-]+$/.test(normalized) ? normalized : sha256(normalized).toString('hex');
return `profile:${stable}`;
}
2. Tenant-aware caching
Include tenant identifiers in cache keys to prevent cross-tenant data exposure. If your app resolves tenant from request headers or subdomains, incorporate it into the key.
function cacheKeyForUserData(tenantId: string, userId: string): string {
const safeTenant = tenantId.trim().toLowerCase();
const safeUser = userId.trim().toLowerCase();
return `tenant:${safeTenant}:user:${safeUser}:profile`;
}
3. Validate and sanitize cached responses
Before using cached data, validate its structure and ownership. Do not trust cached entries to contain only expected fields.
import { DynamoDBClient, GetItemCommand } from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({});
async function getValidatedProfile(cacheKey: string, userId: string) {
const cached = await cache.get(cacheKey);
if (cached && cached.userId === userId && typeof cached.name === 'string') {
return cached;
}
// Fallback to DynamoDB
const cmd = new GetItemCommand({
TableName: 'Profiles',
Key: { userId: { S: userId } },
});
const { Item } = await client.send(cmd);
if (!Item) return null;
const profile = {
userId: Item.userId.S,
name: Item.name.S,
email: Item.email?.S ?? null,
};
await cache.set(cacheKey, profile, { ttl: 300 });
return profile;
}
4. Use parameterized queries and avoid concatenation
When constructing queries that inform cache behavior, use condition expressions and key condition expressions with placeholders rather than string concatenation to avoid injection-style cache manipulation.
import { QueryCommandInput } from '@aws-sdk/client-dynamodb';
const queryInput: QueryCommandInput = {
TableName: 'Articles',
KeyConditionExpression: 'authorId = :authorId',
ExpressionAttributeValues: {
':authorId': { S: authorId.trim() },
},
};
5. MiddleBrick integrations
Use the middleBrick CLI to validate your endpoints against cache poisoning indicators. Run middlebrick scan <url> to get findings on input validation and data exposure related to caching behavior. If you automate security in pipelines, the GitHub Action can fail builds when risk scores exceed your threshold, and the Pro plan’s continuous monitoring can schedule regular scans as your API evolves.