The client is always untrusted: designing systems that assume compromise
A customer's browser, a partner's API call, and a shared terminal are all attacker-reachable. The discipline that keeps a critical system safe is deciding what is allowed to run there, and what never can.
Security Practice
Every system that touches the outside world has a soft edge: the device or the caller you do not control. It runs code you did not write, on a network you cannot see, possibly while someone is actively trying to subvert it. The single most important architectural decision in a high-stakes system is to treat that edge as hostile by default, and to design as if it is already compromised.
This is not pessimism. It is the only assumption that survives contact with reality. A determined actor can read, modify, and replay anything the client can see. So the rule we hold to is simple to state and demanding to keep: security-relevant logic lives on the server, and only on the server.
What must never ship to the client
The temptation is always to validate on the client because it feels faster and the code is right there. Client-side checks are fine for one thing only: helping an honest user fix a mistake before they submit. They can never be the thing that gates a decision.
- Validation that gates an outcome. The client can show a friendly error; the server re-decides from scratch and is the only authority.
- Risk scoring and eligibility rules. If the logic that approves, flags, or prices something is in the bundle, it can be read and evaded. It belongs in a network the client cannot reach.
- Authorization. Whether a caller may see or change a record is a server decision, recomputed on every request, never inferred from a hidden field or a role the client claims.
- Secrets and crypto material. No keys, no signing logic that depends on a secret, nothing that a stolen bundle would expose.
Validate twice, trust once
An identity check is a good example. The client can validate the format of an entry so the user gets an instant result. Then the value, never a blind trust in the client's verdict, is sent to the server, which performs the real check against the system of record. If the two disagree, the server wins. The client's job was to be helpful, not to be believed.
Treat the client as a convenience for honest users and a liability for everyone else. Decide accordingly.
The payoff
When the trust boundary is drawn correctly, a compromised client is an inconvenience, not a breach. The attacker controls their own screen and nothing else. The logic they wanted to study is not there to study. The rule they wanted to bypass is recomputed on a server they cannot reach. This is what it means to assume compromise and still sleep at night.