Cache Poisoning in Nestjs with Mutual Tls
Cache Poisoning in Nestjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker manipulates a cache so that subsequent requests receive malicious or incorrect responses. In a NestJS application using mutual TLS (mTLS), the presence of mTLS can inadvertently make caching behavior less scrutinized because communication is assumed to be secure and authenticated. If the application caches responses based on request attributes that include client identity or authorization information but does not include the client certificate or the resolved principal in the cache key, one authenticated client may receive another client’s cached response.
With mTLS, the server validates the client certificate, but if the caching layer (for example, an in-memory or reverse proxy cache) uses only the request URL, query parameters, or a subset of headers as the cache key, it may treat responses as safe to reuse across different mTLS-authenticated principals. This is a Cache Poisoning vector in the context of mTLS: the server returns a cached response that was originally generated for client A to client B, potentially exposing data or executing logic under the wrong identity. NestJS does not inherently provide application-level cache poisoning protections; developers must ensure that any caching strategy accounts for mTLS-bound identities when constructing cache keys.
Common scenarios include:
- Caching keyed only by the request URL while neglecting the mTLS-derived principal (the certificate subject or mapped user), enabling horizontal data exposure across authenticated clients.
- Caching responses that include role or group claims extracted from the client certificate, but failing to include those claims in the cache key, which can cause privilege confusion if a cached response meant for an admin client is served to a non-privileged client.
- Using shared caches in front of NestJS services without ensuring that mTLS metadata is part of the cache key, which can lead to sensitive data being cached and inadvertently served to unintended clients within the same environment.
To detect this class of issue, scans examine whether authenticated endpoints with mTLS expose cross-client data via caching behavior, and whether cache keys incorporate identity derived from mTLS. Remediation focuses on ensuring that cache keys include the mTLS principal or other strong per-client attributes and that sensitive data is never cached in a way that ignores client context.
Mutual Tls-Specific Remediation in Nestjs — concrete code fixes
Remediation in NestJS involves explicitly including mTLS-derived identity in cache keys and enforcing strict per-client caching behavior. Below are concrete, working examples that show how to extract the client certificate and use its fields in cache keys, and how to configure HTTP-level caching to avoid cross-client contamination.
Extracting the client certificate in NestJS
In a typical mTLS setup, the client certificate is available in the request object under req.socket.getPeerCertificate(). You can create a NestJS interceptor or a custom decorator to read the certificate and expose the identity for use in cache keys.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/observables/map';
@Injectable()
export class MtlsIdentityInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const request = context.switchToHttp().getRequest();
const cert = request.socket.getPeerCertificate();
// Normalize and map certificate fields to an identity string suitable for cache keys
const fingerprint = cert.fingerprint || 'no-cert';
const subject = cert.subject || {};
const userId = subject.CN || subject.commonName || fingerprint;
request.mtlsIdentity = { fingerprint, userId };
return next.handle().pipe(map(data => data));
}
}
Apply this interceptor globally or on specific controllers that require identity-aware caching:
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: MtlsIdentityInterceptor,
},
],
exports: [MtlsIdentityInterceptor],
})
export class AppModule {}
Cache key construction that includes mTLS identity
When using a caching provider (e.g., a simple in-memory cache or an external cache via a library), incorporate the mTLS identity into the cache key to ensure isolation between clients.
import { Injectable } from '@nestjs/common';
@Injectable()
export class CacheService {
private store = new Map<string, any>();
key(request: any): string {
const base = request.originalUrl || request.url || '*';
const identity = request.mtlsIdentity || { userId: 'anonymous' };
// Include userId or fingerprint to isolate per-client caches
return `${base}:userId:${identity.userId}`;
}
get(request: any): any {
return this.store.get(this.key(request));
}
set(request: any, value: any, ttlMs?: number): void {
this.store.set(this.key(request), value);
// Implement TTL as needed
}
}
Use this cache service in your controllers or services rather than relying on framework-managed caches that may not account for mTLS context.
HTTP-level caching guidance with mTLS
If you rely on reverse proxies or CDNs for caching, ensure that mTLS-derived attributes are included in cache keys at the proxy level and that sensitive data is not cached for shared paths. In NestJS, prefer application-level caching for sensitive endpoints and configure short or no caching for responses that contain per-client data. For public, non-sensitive data, you may set cache headers, but always validate that the cache key includes the mTLS identity to prevent cross-client poisoning.