Cache Poisoning in Strapi (Typescript)
Cache Poisoning in Strapi with Typescript — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker manipulates cached data so that malicious content is served to users. In Strapi, a common pattern is to cache the result of database queries or computed values using response or data-layer caching. When these cached values are derived from user-controlled inputs without strict validation or normalization, the cache may store attacker-influenced content and serve it to subsequent requests.
With Strapi, many developers use Typescript to define content types, services, and controllers. If a service builds cache keys or cached payloads using raw parameters (e.g., query strings or headers) without sanitization, an attacker can inject values that change the cache key or the cached representation. For example, a Strapi endpoint that includes a custom header like X-Store-Key in the cache key may unintentionally allow an attacker to overwrite entries used by other users. Because Strapi’s runtime serves cached responses directly, poisoned cache entries can lead to information disclosure, incorrect data being returned, or even the injection of executable code when the cached output is later consumed by client-side templates.
Typescript does not prevent cache poisoning by itself; it depends on how the code is written. In Strapi, a controller written in Typescript might read a query parameter and use it to decide which variant of a response to cache. If the parameter is used verbatim, an attacker can supply a crafted value that results in a different cache entry. Consider a product listing endpoint where the language is passed as a query parameter and used as part of the cache key. Without normalization, variants like en, en-US, or even injected headers can create distinct cache entries, enabling an attacker to poison entries for other locales. Because Strapi’s caching may be implemented in custom Typescript services, developers must ensure that any user-influenced data contributing to cache keys or cached content is validated, normalized, and isolated per tenant or user context.
In addition to key manipulation, cache poisoning in Strapi with Typescript can arise from unsafe composition of cached fragments. If a service merges user-supplied values into cached objects and then stores the merged result, an attacker might inject fields that later appear in responses. For example, extending a cached entity with attacker-controlled properties via spread operators can lead to unintended data exposure when the cached object is serialized and served. The combination of Strapi’s flexible content modeling and permissive Typescript typing can amplify these risks if developers assume that cached structures are immutable or trustworthy.
Because middleBrick scans the unauthenticated attack surface and includes checks for Data Exposure and Input Validation, it can identify endpoints where cache-related parameters are reflected in responses or where unsafe caching patterns exist. The LLM/AI Security checks further probe for indirect risks such as prompt injection that could leverage poisoned data paths. By running scans against the public endpoint, teams can detect whether cache poisoning vectors are present in Strapi implementations, even when authentication is not required.
Typescript-Specific Remediation in Strapi — concrete code fixes
To mitigate cache poisoning in Strapi with Typescript, focus on strict input validation, canonicalization of cache keys, and isolation of cached data. Avoid directly using raw query parameters or headers in cache identifiers. Instead, normalize inputs and derive cache keys using a deterministic, sanitized representation. Below are concrete Typescript examples showing insecure patterns and their remediated alternatives within Strapi.
Insecure example where a raw query parameter influences cache behavior:
// src/api/product/controllers/product.controller.ts
import { sanitizeEntity } from 'strapi-utils';
export default {
async find(ctx) {
const { lang = 'en' } = ctx.query;
const cacheKey = `products:${lang}`; // Vulnerable: raw user input in cache key
const cached = await strapi.cache.get(cacheKey);
if (cached) return cached;
const products = await strapi.entityService.findMany('api::product.product');
const safeLang = ['en', 'fr', 'de'].includes(lang) ? lang : 'en'; // basic allowlist
const normalizedKey = `products:${safeLang}`;
await strapi.cache.set(normalizedKey, products, { ttl: 300 });
return products;
},
};
Remediated version with normalized cache keys and strict validation:
// src/api/product/controllers/product.controller.ts
import { sanitizeEntity } from 'strapi-utils';
const ALLOWED_LOCALES = new Set(['en', 'fr', 'de']);
function getLocale(ctx: any): string {
const raw = ctx.query.lang;
if (typeof raw === 'string' && ALLOWED_LOCALES.has(raw)) {
return raw;
}
return 'en';
}
export default {
async find(ctx) {
const locale = getLocale(ctx);
const cacheKey = `products:locale:${locale}`; // Safe: canonicalized key
const cached = await strapi.cache.get(cacheKey);
if (cached) return cached;
const products = await strapi.entityService.findMany('api::product.product');
await strapi.cache.set(cacheKey, products, { ttl: 300 });
return products;
},
};
When merging user data into cached objects, avoid spreading untrusted input directly. Instead, explicitly pick safe properties:
// src/api/cart/services/cart.ts
export async function getCart(userId: string, overrides: Partial<Cart> = {}) {
const base: Cart = {
items: [],
total: 0,
currency: 'USD',
};
const cached = await strapi.cache.get(`cart:${userId}`);
if (cached) return { ...base, ...cached };
return base;
}
// Unsafe merging pattern to avoid:
// const merged = { ...cached, ...overrides };
// Safe explicit merge:
export function mergeCart(existing: Cart, updates: Partial<Cart>): Cart {
return {
items: Array.isArray(updates.items) ? updates.items : existing.items,
total: typeof updates.total === 'number' ? updates.total : existing.total,
currency: typeof updates.currency === 'string' ? updates.currency : existing.currency,
};
}
For endpoints that use custom Typescript services, enforce schema validation on any data that may influence caching or be stored in cache. Using libraries like zod or class-validator ensures that only expected shapes are accepted, reducing the risk that poisoned values alter cache behavior.
middleBrick’s scans verify whether endpoints reflect unsafe parameters in responses and whether input validation is insufficient. By combining these scans with the remediation patterns above, teams can reduce the likelihood of cache poisoning in Strapi applications that rely on Typescript for business logic.