THIS EXPLANATION
THE ROOM
CDA·254 Computing, Data & AI 7 MIN · 8 STATIONS

Warm reuse leaks

A Socratic walk-through of warm reuse leaks — reasoned out one step at a time, not lectured.

abcdefgh
a

The question we started with

THE QUESTION #

Why does a system that reuses its own leftovers to go faster start returning one user's data to another?

Reuse is the least controversial optimisation there is. Keep the connection pool open, keep the parsed config, keep the compiled template, keep the model in memory — the second request is fast because it inherits what the first one built.

And yet a recognisable class of serious incident begins exactly here: a user receives another user's dashboard, invoice, or profile. Not through an injection flaw or a broken permission check, but through a system that was being efficient. The question worth sitting with is why the same mechanism produces both the speed and the leak, because if they were separable somebody would have separated them.

b

Reasoning it through

REASONING #

Ask what "warm" means. A process, container, or isolate handled a request and was not destroyed, so whatever it wrote into memory is still there when the next request arrives. That persistence is the entire benefit — and it is unconditional: memory does not distinguish what you meant to keep from what you happened to leave.

So the safety of warm reuse rests on a classification. Some state is a property of the deployment — a connection pool, a parsed config, a compiled regular expression, model weights — identical for every request, so sharing it is right. Other state is a property of one request: the caller's identity, their tenant, the row they asked for. Sharing that is a data breach.

Now ask what enforces the classification. Nothing does. Both kinds live in the same memory with the same lifetime, and a module-scope variable holding a database client is indistinguishable to the runtime from one holding the current user. The boundary between "cache" and "leak" exists only in the developer's intent, and intent is not a mechanism.

Which is why the failure mode is so consistent in shape. Somebody writes request-scoped data into a location whose lifetime outlasts the request, and there are only a handful of such locations: a module-level variable used as a convenience handle; a shared client mutated per request rather than constructed per request, of which setting an auth token on a shared HTTP client is the classic; a cache keyed on something that omits the tenant, so two users' entries collide; a temporary file left on local disk, which many platforms preserve between invocations; a background task that outlives the response and finishes writing after the next request has started.

Notice the common structure. In every case the value was correct when written. The defect is not a wrong value but a value with the wrong lifetime — and lifetimes are invisible in most code, which is why this survives review: the line that assigns looks fine, the line that reads looks fine, and no single line is wrong.

Then there is the reason it survives testing, which is the more dangerous half. A cold instance has no leftovers, so the first request always behaves correctly, and a test suite that starts fresh exercises precisely the case that cannot fail. The bug needs two requests, from different principals, on the same warm instance, in that order — common in production, nearly absent from a test environment. This is path dependence in a strict sense: the output depends on the history of the process, and that history is not part of any input you thought to vary. The concurrency variant is worse again, since on a runtime handling overlapping requests in one process, request B can read what request A wrote a microsecond ago, in a timing-dependent window that is close to impossible to reproduce.

Is there a principled fix rather than a list of don'ts? It follows from the analysis: make lifetime explicit. Give each request its own container for its own state — a context object passed down the call chain, or the runtime's request-scoped storage — and make anything shared immutable after construction. Immutability is the load-bearing part: a value that cannot be written during a request cannot carry anything out of it, which converts an ambient risk into a structural guarantee. Where a cache genuinely must hold per-user data, put the principal in the key.

c

The analogy

THE ANALOGY #
THE FIGURE

It is a shared workbench between shifts. Leaving the vice bolted down, the jigs set, and the tools on the wall is exactly why the next shift starts fast. The danger is the offcut left clamped in the jig and the customer's order slip left on the bench — both left in the same place as the tools, both for the same reason, and nothing about the bench says which items were meant for everyone and which belonged to the last job.

WHERE IT BREAKS DOWN

a worker glancing at a bench notices a stray order slip because it looks out of place, whereas a process reading a variable has no perception at all — so the mistake a person would catch by sight is, in software, silent by construction.

d

Clarifying the model

THE MODEL #

Three refinements.

First, the answer is not "stop reusing." Rebuilding a connection pool per request would be catastrophic for latency and for the database; the shared-state design is correct. What must change is that shared state be immutable after construction, and per-request state get a per-request home.

Second, the danger is not confined to two users of your own application. On a multi-tenant platform, leftovers can cross a trust boundary through the platform's own reuse, which is why isolation technologies assume one tenant's memory must never be visible to another's — and why the processor side-channel vulnerabilities of recent years were treated so seriously, since they attacked that assumption from below.

Third, an honest limit on tooling: static analysis catches some of this — a mutable module-level variable written inside a handler is detectable — but it cannot generally tell a legitimate cross-request cache from an accidental one, because the distinction is semantic. Load tests with distinct authenticated principals catch more in practice than any linter.

e

A picture of it

THE PICTURE #
Warm reuse leaks
Warm reuse leaks The centre is the one fact producing both the benefit and the hazard: state survives the request that made it. The first branch is what you intended to survive, and why warm instances are fast. The second is what survived anyway -- each item a real place where request-scoped data acquires too long a lifetime. The third branch is the point of the picture: nothing in the runtime separates the first two branches except intention. The fourth explains why the defect reaches production, since the cheapest test is exactly the case that cannot expose it. {"generator":"[email protected]","source":"../Socrates/.diagram-cache/_src/warm-reuse-leaks.md","sourceIndex":1,"sourceLine":4,"sourceHash":"3834a949ea50391438e3893c0054a869afd12b903ba79c95618f026f8ebbeeb4","diagramType":"mindmap","layoutVariant":"source","repairedDuplicateIds":[{"original":"mermaid-3834a949ea503914-0-node_1","replacement":"mermaid-3834a949ea503914-0-node_1--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_2","replacement":"mermaid-3834a949ea503914-0-node_2--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_3","replacement":"mermaid-3834a949ea503914-0-node_3--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_4","replacement":"mermaid-3834a949ea503914-0-node_4--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_5","replacement":"mermaid-3834a949ea503914-0-node_5--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_6","replacement":"mermaid-3834a949ea503914-0-node_6--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_7","replacement":"mermaid-3834a949ea503914-0-node_7--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_8","replacement":"mermaid-3834a949ea503914-0-node_8--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_9","replacement":"mermaid-3834a949ea503914-0-node_9--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_10","replacement":"mermaid-3834a949ea503914-0-node_10--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_11","replacement":"mermaid-3834a949ea503914-0-node_11--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_12","replacement":"mermaid-3834a949ea503914-0-node_12--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_13","replacement":"mermaid-3834a949ea503914-0-node_13--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_14","replacement":"mermaid-3834a949ea503914-0-node_14--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_15","replacement":"mermaid-3834a949ea503914-0-node_15--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_16","replacement":"mermaid-3834a949ea503914-0-node_16--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_17","replacement":"mermaid-3834a949ea503914-0-node_17--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-node_18","replacement":"mermaid-3834a949ea503914-0-node_18--duplicate-2"},{"original":"mermaid-3834a949ea503914-0-gradient","replacement":"mermaid-3834a949ea503914-0-gradient--duplicate-2"}],"motion":"entrance-with-reduced-motion-fallback","presentation":"editorial","attempt":1,"viewBox":{"x":0,"y":0,"width":1193,"height":662},"qa":{"passed":true,"findings":[]}} Memory still there whenthe next request arrives Meant to be shared Connection pool Parsed configuration Compiled templates andregexes Loaded model weights Left behind by accident Module variable holdingthe current user Shared client mutatedwith this request token Cache keyed without thetenant Temporary file written tolocal disk Background task stillrunning after theresponse Why the two areindistinguishable Same memory Same lifetime Only intent separatesthem Why tests miss it First request on a freshinstance always passes Needs two principals inorder on one warminstance

How to readThe centre is the one fact producing both the benefit and the hazard: state survives the request that made it. The first branch is what you intended to survive, and why warm instances are fast. The second is what survived anyway — each item a real place where request-scoped data acquires too long a lifetime. The third branch is the point of the picture: nothing in the runtime separates the first two branches except intention. The fourth explains why the defect reaches production, since the cheapest test is exactly the case that cannot expose it.

f

What became clearer

WHAT CLEARED #
WHAT CLEARED

Speed and leakage come from one mechanism because they are one mechanism: memory that outlives a request. What distinguishes a cache from a breach is not where the value is stored or how it is written but whose it is, and no runtime knows that — so the classification lives only in a developer's head and fails silently when wrong. The defect is not a wrong value but a value with the wrong lifetime, which is why it reads as correct in review and passes every test starting from a cold instance. The structural fix is to stop relying on intent: make shared state immutable after construction, give per-request state an explicit per-request home, and put the principal in any key that could otherwise collide.

g

Where to go next

ONWARD #
  • How request-scoped storage is implemented on runtimes with overlapping requests, and what it costs.
  • How to construct a load test that would actually fail on a cross-request leak, using distinct authenticated principals.
h

Key terms

TERMS #
TermWhat it means
Warm instancean execution environment retained after a request completes so the next can reuse what it built.
Request-scoped statedata tied to one caller and one request, which must not outlive it.
Module-scope variablea value whose lifetime is the process, not the request; the commonest accidental home for request-scoped data.
Cache key collisiontwo principals sharing a key because it omits the tenant, so one is served the other's data.
Path dependencebehaviour depending on the history of prior executions rather than on the current input alone.

Every term the collection defines is gathered in the glossary.

Nearby on the shelf

4