HIGH cache poisoningsailsjavascript

Cache Poisoning in Sails (Javascript)

Cache Poisoning in Sails with Javascript — how this specific combination creates or exposes the vulnerability

Cache poisoning in Sails with JavaScript occurs when an attacker influences cached responses so that subsequent users receive malicious or altered data. Because Sails is a Node.js MVC framework commonly used to build JSON APIs, caching behavior is often implemented at the service or controller layer using JavaScript logic and HTTP caches, CDNs, or reverse proxies like Varnish or Nginx. If user-specific or untrusted input is used to construct cache keys or cache-control directives, an attacker can cause a poisoned cache entry to be shared across users.

In Sails, a typical pattern is to cache rendered JSON responses or EJS/HTML fragments using response headers set in controller actions. For example, returning res.cache() with custom headers or using res.set() to set Cache-Control can inadvertently cache responses that vary by authorization, tenant, or user input. If the cache key does not include an authenticated user identifier or a tenant context, one user’s sensitive data can be cached and then served to another user. This is a BOLA/IDOR-like exposure amplified by caching, and can lead to sensitive data exposure or incorrect application behavior.

Input validation weaknesses also contribute to cache poisoning. If an endpoint accepts query parameters that influence the response but are not strictly validated or normalized, an attacker can craft values that change the response enough to produce a distinct cache entry that others subsequently receive. Because Sails applications often serve APIs consumed by multiple clients, a poisoned cache entry can propagate quickly. In addition, if the application sets permissive Vary headers or omits important request attributes from the cache key (such as authentication tokens or tenant IDs), intermediaries may store and serve responses that should be private. The runtime scanning checks in middleBrick test for these input validation and data exposure risks by analyzing the OpenAPI spec and correlating runtime behavior, helping to identify endpoints where cache-related misconfigurations exist.

Javascript-Specific Remediation in Sails — concrete code fixes

To mitigate cache poisoning in Sails with JavaScript, ensure cache keys and cache-control directives account for authentication, tenant context, and validated input. Avoid using raw user input in cache keys and normalize parameters before caching. Below are concrete examples showing insecure patterns and their secure equivalents.

Insecure example: caching without tenant or user context

// api/controllers/ReportController.js
module.exports = {
  async showReport(req, res) {
    const { reportId } = req.params.all();
    // BAD: cache key depends only on reportId; tenant/user not considered
    const cached = await CacheService.get(`report:${reportId}`);
    if (cached) return res.json(cached);

    const report = await Report.findOne(reportId);
    await CacheService.set(`report:${reportId}`, report, { ttl: 300 });
    return res.json(report);
  }
};

Secure example: include tenant and user identifiers in the cache key and validate input

// api/controllers/ReportController.js
module.exports = {
  async showReport(req, res) {
    const { reportId } = req.params.all();
    const userId = req.user ? req.user.id : null;
    const tenantId = req.tenant ? req.tenant.id : null;

    // Validate and normalize input
    if (!reportId || !Number.isInteger(Number(reportId)) || Number(reportId) <= 0) {
      return res.badRequest('Invalid report identifier');
    }

    // Include tenant and user in cache key to prevent cross-user/poisoning
    const cacheKey = `tenant:${tenantId}:user:${userId}:report:${reportId}`;
    const cached = await CacheService.get(cacheKey);
    if (cached) return res.json(cached);

    const report = await Report.findOne(reportId);
    if (!report) return res.notFound();

    await CacheService.set(cacheKey, report, { ttl: 300 });
    // Explicitly set Vary to indicate key dependencies
    res.set('Vary', 'Authorization, X-Tenant-ID');
    return res.json(report);
  }
};

Insecure example: permissive caching headers with user-specific data

// api/controllers/UserController.js
module.exports = {
  async profile(req, res) {
    const user = await User.findOne(req.user.id);
    // BAD: public caching of user-specific data
    res.set('Cache-Control', 'public, max-age=3600');
    return res.json(user);
  }
};

Secure example: private caching or no-store for sensitive responses

// api/controllers/UserController.js
module.exports = {
  async profile(req, res) {
    const user = await User.findOne(req.user.id);
    // Use private cache or avoid caching sensitive data
    res.set('Cache-Control', 'private, no-store');
    return res.json(user);
  }
};

Additionally, always validate and sanitize query parameters that affect responses. For example, if an endpoint uses a format query parameter to switch response representations, ensure it is enumerated and validated to prevent cache key divergence based on attacker-controlled values.

middleBrick’s scans include input validation and data exposure checks that highlight endpoints where headers or caching behavior may enable poisoning. By combining secure coding practices with continuous scans via the CLI, Dashboard, or GitHub Action, teams can detect and remediate risky cache configurations before they affect production users.

Frequently Asked Questions

How does middleBrick detect cache poisoning risks in Sails APIs?
middleBrick runs 12 parallel security checks including input validation, data exposure, and rate limiting. It analyzes your OpenAPI/Swagger spec (with full $ref resolution) and correlates findings with runtime behavior to highlight endpoints where cache keys or cache-control headers may allow poisoning or cross-user data exposure.
Can the GitHub Action prevent a build if cache-related issues are found?
Yes. The GitHub Action can fail builds when the API’s security score drops below your configured threshold, helping to prevent deployments that include risky caching configurations.