HIGH cache poisoningexpressapi keys

Cache Poisoning in Express with Api Keys

Cache Poisoning in Express with Api Keys — how this specific combination creates or exposes the vulnerability

Cache poisoning in an Express API that uses API keys occurs when an attacker causes cached responses to vary by attacker-controlled data, such as a key value, and those cached responses are later served to other users. This is a BFLA/Privilege Escalation and Property Authorization issue: the cache treats distinct key contexts as equivalent or insufficiently isolated, allowing one user to see another user’s data or elevated behavior. A common pattern is using the API key header to personalize data but forgetting to include it in cache key derivation, so a public or shared cache entry is reused across keys.

Consider an Express endpoint that returns user profile data and uses an API key for access control but does not incorporate the key into the cache key. If the response is cached at a CDN or in application-level caching based only on the request path, an authenticated user could request /profile with their key and receive a valid response cached under that path. A second user could then request /profile without a key or with a different key and receive the first user’s cached data, leading to data exposure. Even when authorization checks happen before caching, if the cache key does not reflect the key context, the authorization boundary is bypassed in the cache layer.

Another scenario involves query parameters that should differentiate cache entries but are omitted from the key. For example, an endpoint /reports?type=summary might return different data per API key, but if the cache key excludes the key header or a tenant identifier, responses become cross-contaminated. This intersects with BOLA/IDOR when the cached resource contains identifiers that should be scoped to a subject but are not properly segregated by key. MiddleBrick detects these cache-poisoning patterns during its 12 parallel checks, including BFLA/Privilege Escalation, Property Authorization, and Unsafe Consumption, by correlating OpenAPI/Swagger definitions with runtime behavior and ensuring that key-sensitive paths are validated for proper isolation.

In the OpenAPI spec, ensure that security schemes for API keys are declared and that paths requiring keys are not marked as non-secured or inconsistently secured. Runtime findings will highlight cases where key-bearing requests produce responses that could be cached without key inclusion. The scanner does not fix the cache configuration but provides remediation guidance, such as incorporating the API key (or a canonical representation of key-scoped data) into the cache key and enforcing strict tenant or subject scoping in cached resources.

Api Keys-Specific Remediation in Express — concrete code fixes

To mitigate cache poisoning when using API keys in Express, ensure the API key (or a normalized key scope) is part of the cache key, and avoid caching responses that contain key-specific data unless the key is guaranteed to be part of the derivation. Below are concrete, secure patterns you can apply.

Example 1: Key-aware cache key generation

Use a caching layer (e.g., Redis) where the cache key explicitly includes the API key or a tenant/subject derived from it. Never use only the route as the cache key when responses vary by key.

const crypto = require('crypto');

function cacheKeyFor(req) {
  // Normalize: include a key-derived component to isolate caches per key scope
  const keyId = req.get('X-API-Key') || 'anonymous';
  const keyHash = crypto.createHash('sha256').update(keyId).digest('hex').slice(0, 16);
  return `profile:${keyHash}`;
}

app.get('/profile', (req, res) => {
  const key = cacheKeyFor(req);
  redis.get(key, (err, cached) => {
    if (cached) {
      return res.json(JSON.parse(cached));
    }
    // fetch user profile bound to the key
    db.getProfileByKey(keyId, (err, profile) => {
      if (err) return res.status(500).json({ error: 'server' });
      redis.setex(key, 300, JSON.stringify(profile)); // cache with key-aware key
      res.json(profile);
    });
  });
});

Example 2: Disallow caching of key-authenticated responses at shared caches

If you must use a shared cache (e.g., CDN), prevent caching when an API key is present, or ensure Vary headers correctly differentiate key contexts. The following Express middleware sets appropriate cache-control headers and avoids caching sensitive responses.

app.use((req, res, next) => {
  const hasKey = Boolean(req.get('X-API-Key'));
  if (hasKey) {
    // Do not allow shared caches to store key-specific responses
    res.set('Cache-Control', 'no-store, private');
    res.set('Vary', 'X-API-Key');
  } else {
    // Public caching allowed when no key is present
    res.set('Cache-Control', 'public, max-age=300');
  }
  next();
});

Example 3: Validate key scope before caching in application memory

When implementing in-memory caching, ensure cached objects are tagged with the key scope and that retrieval validates the same scope. This prevents cross-key contamination in the same process.

const cache = new Map();

app.get('/data', (req, res) => {
  const keyId = req.get('X-API-Key');
  if (!keyId) return res.status(401).json({ error: 'missing key' });
  const cacheKey = `data:${keyId}`;
  const entry = cache.get(cacheKey);
  if (entry) {
    return res.json(entry);
  }
  // compute and store scoped data
  const data = computeDataForKey(keyId);
  cache.set(cacheKey, data);
  res.json(data);
});

These patterns address BFLA/Privilege Escalation and Property Authorization by ensuring cached data cannot be retrieved across distinct API key contexts. They complement middleBrick’s checks, which include API key–specific tests for BFLA/Privilege Escalation and Unsafe Consumption; findings will point to missing key inclusion in cache derivation and suggest incorporating the key into cache keys and Vary headers.

Frequently Asked Questions

If my Express API uses API keys in headers, do I need to include them in cache keys?
Yes. To prevent cache poisoning, include the API key (or a normalized, key-scoped identifier) in your cache key so responses are isolated per key context.
Should I allow caching of responses when an API key is present?
Generally, avoid caching responses when an API key is present, or ensure Vary includes the key and the cache is scoped to that key. Use no-store for sensitive key-bound responses.