Cross-Tenant Invoice Cache Disclosure
Northwind Health runs a multi-tenant patient portal on Next.js and an API gateway. Tenants are selected by an X-Tenant-ID header set by the gateway after validating a session cookie; the API does not otherwise scope database queries because a shared library appends tenant_id. A CDN caches GET responses for 60 seconds. Redis caches API JSON for 10 minutes using the key GET:{path}. A bug bounty report shows a user at Tenant A could load /api/invoices/current/pdf and, after three refreshes, receive an invoice PDF belonging to Tenant B. The reporter used two accounts and a scripted 200 ms loop. No admin access and no leaked credentials were involved. The portal serves 1.2M patients across 40 tenants; invoice PDFs include name, address, and diagnosis codes. The on-call engineer proposes disabling Redis and purging the CDN. Product wants a hotfix in 4 hours.
1. Classify the bug and identify the root cause. Address whether the reporter's refresh loop was necessary.
Read the worked answer
This is a cross-tenant cache disclosure, not an authentication bypass. The session was valid and the gateway authenticated the user; authorization depended on tenant context that was not part of the cache key. The endpoint resolves current from the session, so every tenant's request maps to the same Redis key GET:/api/invoices/current/pdf. Whichever response populated the key was served to later requesters. The CDN can do the same if it caches the authenticated response without a tenant- or user-varying key. The 200 ms loop mattered because it increased the chance of hitting a warm key and racing concurrent requests, but it is not required in principle: a single request after Tenant B's request could leak. Severity is high because the PDFs contain PHI and the reporter demonstrated cross-tenant access without credentials. Do not label it cache poisoning unless an attacker can deliberately seed a chosen response; this is cache-key omission.
2. The on-call engineer proposes disabling Redis only. Does that contain the issue? What 4-hour hotfix would you approve?
Read the worked answer
Disabling Redis is necessary but not sufficient. The CDN has a 60-second cache and may already hold responses; browsers or reverse proxies may also cache. Approve a hotfix that: (1) purges the CDN for /api/invoices/* and flushes the Redis namespace used by authenticated APIs; (2) sets Cache-Control: private, no-store and Vary: Cookie, X-Tenant-ID on authenticated responses; (3) bypasses shared caching for the invoice route at the gateway; (4) adds a server-side authorization check that the invoice's tenant equals the session tenant before rendering. Vary on Cookie alone fragments cache by session and is not a durable multi-tenant design; it is only a stopgap. After the hotfix, rebuild cache keys as tenant_id:user_id:route:params or, better, do not cache per-user sensitive objects. Validate with two accounts across three tenants and confirm no cross-tenant hit in CDN and Redis logs.
3. What evidence and detection would you use to scope exploitation beyond the bug bounty reporter?
Read the worked answer
Preserve CDN logs, Redis keyspace and hit logs, gateway access logs with request IDs, and application logs that record session tenant and invoice tenant. Join on request ID and look for responses where the returned invoice tenant differs from the session tenant; a single mismatch is evidence. Search for repeated /current/pdf requests across tenants, especially from the reporter's IP and ASN. Check CDN cache HIT/MISS and Age headers; Redis MONITOR is too invasive in production, but slowlog and key patterns help. Add a canary tenant with a synthetic invoice and alert on any access from another tenant. Add an application assertion that logs and blocks tenant mismatch before response. Notify privacy and legal if PHI exposure is confirmed; the lack of a report does not mean no access.