Cache Poisoning in Feathersjs (Javascript)
Cache Poisoning in Feathersjs with Javascript
Cache poisoning in a Feathersjs application using JavaScript occurs when an attacker manipulates cache keys or cacheable responses so that subsequent requests receive malicious or incorrect data. Feathers services often rely on external HTTP data sources or computed values that may be stored in a caching layer. If cache keys are built from unvalidated inputs—such as query parameters, headers, or route segments—an attacker can force the server to store and later serve attacker-controlled content.
For example, consider a Feathers service that proxies external API responses and caches them by request URL. If the service uses raw user input to form the cache key without normalization or validation, an attacker can send requests that differ only in casing, encoding, or path structure to poison the cache under multiple keys. In JavaScript, concatenating strings to build cache keys without canonicalization is risky:
// Risky: cache key built directly from user-controlled query parameters
const getCacheKey = (params) => `resource:${params.id}:${params.query}`;
app.service('items').hooks({
before: {
async find(context) {
const key = getCacheKey(context.params.query);
const cached = await cache.get(key);
if (cached) return JSON.parse(cached);
const result = await context.app.service('external').find(context.params);
await cache.set(key, JSON.stringify(result));
return result;
}
}
});
An attacker could supply id=1 and id=1%20OR%201=1 or use different encodings to create distinct cache entries that persist malicious data. Additionally, if the cached response includes sensitive headers or cookies, downstream users may receive unintended information. Feathers hooks running in JavaScript also commonly merge user-supplied headers into cache logic without sanitization, compounding the risk.
The LLM/AI Security checks included in middleBrick specifically test for scenarios where system prompt leakage or unsafe consumption patterns could expose sensitive logic or cached data. These checks highlight risks when AI-assisted code generation suggests naively caching responses keyed by raw input.
When integrating third-party data sources, ensure cache keys are deterministic, normalized, and scoped to the operation and identity of the requester. Avoid caching responses that vary by authorization context unless the cache key explicitly includes tenant or user identifiers in a consistent form.
Javascript-Specific Remediation in Feathersjs
To mitigate cache poisoning in Feathersjs with JavaScript, normalize inputs before constructing cache keys and avoid caching responses that depend on mutable or attacker-influenced data. Use strict schema validation for query parameters and headers, and scope caches by authenticated tenant or user identifiers where applicable.
Below is a hardened example using a consistent hashing approach and schema validation to reduce variability in cache keys:
// Safe: canonicalize and scope cache keys, validate inputs
const crypto = require('crypto');
const Ajv = require('ajv');
const ajv = new Ajv();
const validate = ajv.compile({
type: 'object',
properties: {
id: { type: 'string', pattern: '^[a-f0-9-]+$' },
query: { type: 'object', additionalProperties: true }
},
required: ['id'],
additionalProperties: false
});
const getSafeCacheKey = (params) => {
if (!validate(params)) {
throw new Error('Invalid parameters');
}
const normalized = {
id: params.id.toLowerCase().replace(/\s+/g, ''),
// deterministic serialization of query filters
q: JSON.stringify(params.query || {})
};
const hash = crypto.createHash('sha256').update(JSON.stringify(normalized)).digest('hex');
return `feathers:cache:resource:${hash}`;
};
app.service('items').hooks({
before: {
async find(context) {
const key = getSafeCacheKey(context.params.query);
const cached = await cache.get(key);
if (cached) return JSON.parse(cached);
const result = await context.app.service('external').find(context.params);
await cache.set(key, JSON.stringify(result), { ttl: 60 });
return result;
}
}
});
This approach ensures that the cache key is derived from a canonical representation of the parameters, reducing the risk of equivalent but distinct keys causing poisoned entries. Additionally, you should avoid caching responses that include private headers or cookies and explicitly exclude sensitive context fields from the key computation.
In a multi-tenant setup, include a tenant identifier in the cache key to prevent cross-tenant contamination:
const getSafeCacheKey = (params, tenantId) => {
if (!validate(params)) throw new Error('Invalid parameters');
const normalized = {
tenant: tenantId.toLowerCase(),
id: params.id.toLowerCase().replace(/\s+/g, ''),
q: JSON.stringify(params.query || {})
};
const hash = crypto.createHash('sha256').update(JSON.stringify(normalized)).digest('hex');
return `feathers:cache:resource:${hash}`;
};
Finally, review cached responses to ensure they do not inadvertently store sensitive data such as API keys or PII, which could be exfiltrated via cache poisoning. middleBrick’s Data Exposure and Encryption checks can help identify whether cached responses leak confidential information.