A static variable is the cheapest cache anyone will ever write. It costs a single keyword, it survives every request, and in a quick benchmark it beats every alternative on the shelf. On a server it is also shared by every user that process is handling at that moment, a fact everybody knows and almost nobody is thinking about at the point they reach for it.
A European banking group's IT services subsidiary commissioned a post-delivery review of an application it had sourced to an external supplier. The review covered the contract, the project record, interviews with both sides, and a technical read of the delivered code, including a difference test between two builds taken some weeks apart. Under thread safety, the technical findings record the use of static variables at the server side to store session state, with the stated consequence that behaviour becomes erratic when multiple sessions are active, and that one session may have access to the data of another.
Nothing in that review establishes that one customer's data ever actually reached another customer. It establishes that the code made it possible. That distinction matters procedurally and not at all architecturally: the remediation is identical either way, and so is the lesson.
The challenge
Read the technical findings in order and they look like ordinary code-quality complaints. Caching was done case by case rather than as a policy, and the local cache was used less for efficiency than as a shared scratch space any part of the application could read or write. Data arriving from the server was written into it without the code waiting on that data knowing whether it had arrived, so reads and writes landed in whatever order the network gave them and the same screen sometimes showed old values and sometimes new. Errors were swallowed by conditional statements that checked whether the next statement could run and quietly skipped it when it could not. Packages of up to 100KB were pushed back and forth on ordinary calls because the application had no settled idea of where its data lived.
Every one of those is a maintainability complaint. Put them together and they describe an application with no owner for a single question: what belongs to whom, and for how long.
The static variable is where that question stops being about maintenance. A static field belongs to the process. Session state belongs to the request. Storing the second in the first means the slot every user writes to is the slot every other user reads from, and that is true from the first line of the implementation, not as a result of anything going wrong later.
What makes it worth writing about is that it looks reasonable at the moment it is taken. Threading session context down through the layers of an application is tedious work that shows up in no demo. A static is available everywhere, immediately, with no plumbing. In a codebase already moving data context back and forth on every call, a developer looking to stop that traffic finds the static variable sitting right there, and the change reads as a performance improvement in the pull request, because that is what it is. It is also a confidentiality defect, and nothing in the ordinary review path is set up to say so out loud.
The approach
Functional testing asks whether the application does the right thing for a user. Session isolation is not something the application does. It is a property of the relationship between two users at the same time, and a test written for one user cannot express it, however many of them there are. A suite can be green, complete against every acceptance criterion, and completely silent on this.
In this codebase the suite was not complete either. Automated coverage sat at roughly 10 to 15 percent, and the reviewers, comparing two builds separated by some weeks, found no improvement in quality sufficient to justify moving into user acceptance testing. But the coverage number is a distraction. Take it to ninety percent and the defect still passes, because every test in the suite is one session deep.
The same review found the shape repeating one layer out. A penetration test on the codebase showed that not all service calls checked for the appropriate authorization, and that some were executable by users who did not hold it. The supplier had run neither a vulnerability assessment nor a penetration test of its own. Two findings, one geometry: correct for a single user acting alone, wrong the moment a second user is in the picture.
The third finding is the one that explains the other two. There was no evidence of a system design document, the artefact that would have set out coding standards, design principles and the reasoning behind the approach. That activity had been estimated and priced in the statement of work. A design document is exactly where a property like session isolation gets written down as a property, with a name and an owner. Without one, every developer decides for themselves, in isolation, at speed, and a static variable reads as a performance decision because there is no document anywhere saying it is a security one.
The outcome
This engagement produced findings and recommendations, not a repaired application. The recommendations addressed to the supplier were the unglamorous ones: settle design and coding standards before the work starts, write down which patterns are to be used, let tooling enforce quality rather than goodwill, and hold peer review to a standard. They were sequenced into near-term and longer-term bands. What happened after that sits outside what the review can claim.
The part that travels is the method, and there are three changes we would make to it now.
Name isolation as a design property in the design document, with an owner. If that document does not exist, nothing further on this list is reachable, which is why its absence is the finding underneath the finding.
Put the check in the pipeline rather than in a person. Static analysis that flags mutable state at process scope inside request-handling code is a rule a machine applies to every commit. A reviewer applies it on a good day. The recommendation to use automated tooling was already in this report; what it needed was a specific rule attached to a specific defect class.
Write the test that is two sessions wide. Concurrent sessions carrying distinct data, each asserting it sees only its own. Cheap, boring, and the only kind of test capable of failing on this at all.
None of this stays inside web applications. Any system that holds a per-request identity while running inside a shared process has the same question waiting in it. A feature store serving per-customer features, a scoring service that caches the last customer's context to save a lookup, a model deployment that keeps a warm object between calls: each is the same static variable with better vocabulary and a larger blast radius, because the thing being crossed is no longer a screen but a decision written to a record. Lineage that records which customer's data a given decision was computed from is how anyone finds out, and it has to be designed in, because it cannot be added afterwards to a system that never knew.
The early and cautious language-model pilots teams are running over code review help against local defects: the swallowed error, the duplicated block, the missing type. They are close to useless here, because the property being violated exists nowhere in the file being read. It exists between two executions that no single file describes. That is an architectural read, and it is where our Consult work starts, ahead of any tooling.
A shortcut that reads as a performance habit is a confidentiality defect, and it reached delivery because everyone looking at the build was looking at features. Isolation only holds if somebody declares it, and it only stays true if something checks it on every build.
