Post

Private API Responses and Shared Cache Leaks

Review cache keys, response directives, and authorization order across browsers, proxies, and application caches.

Private API Responses and Shared Cache Leaks

A correct authorization check at the origin cannot protect a response that an intermediary serves to the wrong user without contacting the origin. Shared caching creates another data-access path, and that path needs its own threat model.

Consider /api/invoices/latest. The URL is identical for every tenant, but the response depends on the authenticated identity. A cache keyed only by URL can turn one customer’s successful request into another customer’s disclosure.

Choose an explicit response policy

For a sensitive personalized endpoint that does not need response caching, I would begin with:

1
Cache-Control: no-store

private has a different purpose: it restricts shared-cache storage while allowing private-cache behavior. no-cache permits storage but requires validation before reuse. These directives are not interchangeable; RFC 9111 defines their semantics.

A response directive does not retroactively remove copies already stored under a previous policy. A migration from unsafe caching may need a purge, a versioned route, or another cache-specific invalidation step. Also inspect service workers and application-level caches, which can implement their own storage behavior.

A custom cache needs a complete identity boundary

If a product requires caching authorized data inside the application, define the key from trusted server-side context. A proposed key could include tenant, resource, representation version, and authorization-relevant scope. The caller must still be authorized before receiving the cached result.

Do not use an unvalidated tenant header as the boundary. Do not assume a resource identifier is globally unique if the data model only guarantees uniqueness within a tenant. Cache negative results carefully too: a cached denial can become stale after a legitimate permission change.

Vary can describe representation selection by request headers, but it is not a substitute for an authorization design. Putting credentials into a sprawling cache key also creates operational and privacy problems. Start from the actual shared-cache behavior and a small explicit contract.

Test the path that production uses

I would send requests through the real proxy and CDN configuration using two synthetic tenants. Warm the response with tenant A, request the same URL as tenant B, then repeat after permission changes and logout. Inspect response bodies, cache-status metadata, and origin access logs together.

Include conditional requests. A 304 Not Modified response can cause a client to reuse a stored representation, so validators and authorization must be reviewed alongside ordinary 200 responses. Test error and redirect responses as well as successful ones.

The application owner owns data sensitivity and authorization. The edge-platform owner owns cache configuration and purge behavior. Security brings the cross-identity test cases. Changes to CDN rules should receive the same attention as changes to a repository method that selects customer data.

My rollout condition is demonstrated isolation through the production delivery path. A unit test of the origin handler cannot reveal a shared cache that never reaches that handler on a hit.

This post is licensed under CC BY 4.0 by the author.