Cache Poisoning in Nestjs with Bearer Tokens
Cache Poisoning in Nestjs with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker causes a cache to store malicious or incorrect data, which is then served to other users. In a NestJS application that uses Bearer Tokens for authorization, combining HTTP caching with bearer-based routing or response logic can inadvertently expose sensitive data or enable privilege escalation.
When responses vary by authorization context but are cached without considering the Authorization header, a cached response intended for one user may be served to another. For example, if an endpoint returns user-specific data and the cache key is based only on the request path and query parameters, a token belonging to Alice might be cached. A subsequent request from Bob with a different Bearer token could receive Alice’s cached data, leading to information disclosure (a form of IDOR facilitated by caching).
NestJS does not add caching by default, but developers often introduce caching at the HTTP layer (e.g., reverse proxies, CDNs) or within custom interceptors. If these caches ignore the Authorization header as part of the cache key, bearer-protected endpoints can be vulnerable. Additionally, if a public or shared cache stores responses that should be authenticated, unauthenticated attackers might probe endpoints expecting token-gated data and infer behavior from timing or error differences.
Consider an endpoint that returns account details. If the cache key excludes the Authorization header and a token with elevated scopes is used once, a low-privilege token might receive the cached high-scope response. This violates the principle of least privilege and can expose sensitive fields or operations. The risk is compounded when responses include security-sensitive headers or when caching is applied to endpoints that should always be evaluated in the authentication context.
To detect these issues, scans should compare authenticated and unauthenticated requests to the same endpoint, inspect whether caching headers like Vary are set appropriately, and verify that responses with different authorization contexts are not served from a shared cache. The LLM/AI Security checks in middleBrick can also probe for token handling anomalies and output leakage that may indicate improper cache boundaries.
Bearer Tokens-Specific Remediation in Nestjs — concrete code fixes
Remediation focuses on ensuring cache keys incorporate authorization context and that responses include appropriate cache-control directives. In NestJS, you can customize caching behavior in interceptors or configure your infrastructure to include the Authorization header in cache differentiation.
One approach is to set the Vary header to include Authorization so that caches treat different token contexts as distinct entries. Below is an example using NestJS middleware to add Vary: Authorization for specific routes:
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable()
export class AuthorizationVaryMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void) {
// Ensure caches differentiate by Authorization header presence and value
res.set('Vary', 'Authorization');
next();
}
}
Register the middleware in your application module or bootstrap to apply it globally or to selected routes. This instructs shared caches to maintain separate entries per token, reducing cross-user leakage.
For route-specific handling, you can also set Cache-Control headers directly in a controller, ensuring private responses are not stored in shared caches:
import { Controller, Get, Header, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('account')
export class AccountController {
@Get()
@Header('Cache-Control', 'private, no-store')
getAccount(@Req() req: Request) {
// Returns user-specific data; no-store prevents caching
return { userId: req.user.id, roles: req.user.roles };
}
}
If you must allow caching for performance, use a custom interceptor to key cache entries by the full Authorization header and scope:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { cache } from 'react'; // example; replace with your cache implementation
@Injectable()
export class BearerCacheInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization || '';
const cacheKey = `${request.originalUrl}|${token}`;
// Use cacheKey for storing/retrieving cached responses
return next.handle();
}
}
In addition, ensure that any backend cache or CDN is configured to include the Authorization header in cache keys for endpoints where user context matters. Avoid caching responses that contain sensitive fields unless the cache scope is strictly limited to the token owner.
middleBrick’s scans can validate these configurations by checking whether responses include proper Vary and Cache-Control headers and by comparing outputs across different Bearer tokens to confirm that cached content is not shared across authorization contexts.