Symptom
A security review finds three issues. The batch service runs as root because it needs to bind port 443 at startup. A new tenant’s permissions default to inherited-from-parent, so a misconfigured parent silently grants access. And the authorization logic is spread across four services with a combined 3,000 lines, which nobody claims to fully understand.
Each has a local fix: drop privileges after binding, change the default, write more tests. You ship all three. Six months later there are three new issues with exactly the same shape, in different places.
The individual bugs were not the problem. The problem is that nobody has a rule that would have made them obviously wrong before they were written. That rule set exists, it was published in 1975, and it has needed almost no revision since.
Statement
Saltzer and Schroeder extracted eight design principles from the Multics protection work. They are not theorems and cannot be proved; they are principles, justified by argument and by an unusually consistent record of violations preceding breaches.
- Economy of mechanism. Keep the design as simple and small as possible.
- Fail-safe defaults. Base access decisions on permission, not exclusion: the default is no access.
- Complete mediation. Every access to every object must be checked for authority.
- Open design. The design must not be secret; security must not depend on the attacker’s ignorance of the mechanism.
- Separation of privilege. Where feasible, require two independent keys rather than one.
- Least privilege. Every program and user operates with the minimum privileges necessary to complete the job.
- Least common mechanism. Minimise mechanism shared by and depended upon by multiple users.
- Psychological acceptability. The interface must be easy to use, so that users apply the protections correctly and routinely.
Modern lists add a ninth, defence in depth, which Saltzer and Schroeder gesture at through separation of privilege without naming.
Argument
There is no proof here, so the argument has to be of a different kind: show that each principle is doing real work by exhibiting what its violation costs.
Economy of mechanism, and why it is first. The rest of the list is enforceable only if the mechanism is small enough to reason about. A 3,000-line authorization layer cannot be checked for complete mediation by inspection, which means complete mediation becomes an aspiration. The security-relevant subset of a system is its trusted computing base, and the argument is straightforward: defects scale with size, and any defect in the TCB is a security defect. This is the principle that makes seL4 possible — a microkernel of roughly 10,000 lines is small enough to carry a machine-checked proof, and Linux is not.
Fail-safe defaults, and the asymmetry that justifies it. Consider the two possible failure directions. A default-deny system that is misconfigured denies access that should be allowed: someone files a ticket, it gets fixed, the cost is friction. A default-allow system that is misconfigured grants access that should be denied: nobody files a ticket, because nothing appears broken, and the defect persists until it is exploited. The two errors are not symmetric, because only one of them is self-reporting. That asymmetry is the entire argument, and it is why the tenant-inherits-parent default above is a defect even before anyone shows an exploit. S3 buckets were public by default until 2018, and the resulting decade of data exposures is this principle’s largest natural experiment.
Complete mediation, and the check-then-use gap. Every access, every time. The characteristic violation is caching an authorization decision: check once, reuse the answer. This is TOCTOU (time-of-check to time-of-use) and it is where a large fraction of privilege-escalation bugs live. Every path to an object must pass the check — which in practice means the check belongs at a chokepoint the object cannot be reached around, not at each call site where it can be forgotten. A capability-based design satisfies this structurally: holding the reference is the authorization, so there is no gap between the check and the use.
Open design, which is Kerckhoffs restated for whole systems. Security must rest on the secrecy of keys, not of mechanism. The argument is practical rather than moral: mechanisms leak, through decompilation, insider departure, side channels, and patent filings, whereas keys can be rotated when they leak. A system whose security depends on an unrotatable secret has no recovery path. Every widely-deployed cryptographic standard since DES has been public, and every proprietary “secure” protocol that got serious attention — MIFARE Classic, A5/1, Keeloq — fell within a few years of scrutiny.
Separation of privilege, and why two keys is more than twice one. Requiring two independent authorizations means an attacker must compromise two things, and if the two are genuinely independent, the compromise probabilities multiply rather than add. Concretely: two-factor authentication, two-person rules on production deploys, Shamir-split root keys. The word doing the work is “independent” — a second factor delivered over SMS to a phone number the attacker can port is not a second key, and the multiplication does not apply.
Least privilege, and the confused deputy that shows exactly why. This is the most-quoted principle and the one whose failure mode is least understood, so it gets the full example. Norm Hardy’s 1988 note describes a compiler on a shared system:
- The compiler runs with privilege to write to a billing file,
/sys/billing, because it must record usage. That privilege is legitimate and necessary. - It accepts a user-supplied path for the optimization statistics output.
- A user invokes it with output path
/sys/billing. - The compiler writes there. The access check passes, because the compiler is authorized.
Nothing is broken. No bug was exploited. The compiler used exactly the privilege it was given, on behalf of someone who did not have it. The deputy was not compromised, it was confused: it held authority in an ambient form, attached to its identity, and could not tell which of its authorities the request was supposed to be exercising.
This is the general shape of an enormous family of live vulnerabilities.
Server-side request forgery is a confused deputy — your service can reach the
metadata endpoint and the attacker cannot, so the attacker asks your service to
fetch it. Cross-site request forgery is a confused deputy — the browser attaches
your cookies to a request the attacker composed. sudo misconfiguration is a
confused deputy. So is every “the CI runner can deploy, and anyone can propose a
pipeline change” incident.
The structural fix is not more checks; it is to stop holding ambient authority. If the compiler must be given a writable handle to the output file by the caller, then it can only write where the caller could already write, and the confusion is impossible by construction rather than prevented by vigilance. This is why capability systems are the principled answer to least privilege and access-control lists are the pragmatic one.
Least common mechanism, which reappeared as a hardware problem. Shared mechanism is a channel. The 1975 concern was a shared subroutine or a shared temporary file leaking information between users. The 2018 version is Spectre and Meltdown, where the shared mechanism was the CPU cache and branch predictor — mechanism nobody thought of as protection-relevant because it is invisible to the programming model. Multi-tenant serverless platforms are a large ongoing bet against this principle, and the mitigations, from per-tenant cores to process-per-origin browsers, are all retreats toward it.
Psychological acceptability, the one that was ahead of its time. A protection
that is unusable will be bypassed, and the bypass is the real configuration.
Certificate warnings trained a generation to click through. Password composition
rules produced Password1! on a sticky note. NIST’s 2017 reversal on forced
rotation and composition rules is this principle winning an argument forty years
late. Security that fights the user loses, and the measurable form of the
principle is that the secure path must also be the easy path.
Forbids
Security by obscurity as a primary defence. Not because obscurity has zero value, but because it cannot be the layer that the guarantee rests on.
Default-allow anything. New resources, new tenants, new endpoints, new fields. The default is closed, and the misconfiguration must fail loudly.
Ambient authority in a component that takes untrusted input about where to act. That is the confused-deputy pattern exactly, and it will be exploited.
Monolithic root-privileged processes because dropping privileges is inconvenient. The inconvenience is the entire cost of the principle, and it is smaller than the incident.
Authorization logic too large to audit. If nobody can state the policy, the policy is whatever the code happens to do.
Does not forbid
It does not forbid running as root, which is the misreading that makes people
dismiss the list as impractical. Least privilege is about the privilege held
during an operation, not about the process’s initial identity. A service that
starts as root, binds port 443, and immediately calls setuid to an unprivileged
account satisfies least privilege — the privileged window is a few
instructions, before any untrusted input is read. nginx, OpenSSH, and Postfix all
do this and all are canonical examples of the principle, not violations of it.
It does not forbid caching authorization decisions. Complete mediation is often quoted to condemn any token or session cache, which would make modern authentication impossible. What it requires is that the cached decision be bounded and revocable: short-lived tokens with a revocation check, or cache invalidation on permission change. OAuth access tokens with 5-minute lifetimes and refresh-time re-authorization are a legitimate reading of complete mediation, not a violation of it.
It does not forbid shared infrastructure. Least common mechanism is a warning, not a prohibition; taken absolutely it forbids multi-tenancy, shared libraries, and the operating system. The practical reading is that shared mechanism must be enumerated as attack surface, not that it must be eliminated.
It does not require formal verification. The principles are design heuristics that pay off in ordinary engineering. A reviewer asking “what is the smallest privilege this needs, and who supplies the authority?” is applying the list without a single proof.
It does not make defence in depth redundant with least privilege. They are different: least privilege shrinks the blast radius of one compromise, defence in depth requires several compromises to reach the asset. A system can have one and not the other.
Boundary
- Not proved, and cannot be. These are principles. They organise judgement; they do not decide cases. Two of them will conflict — economy of mechanism against defence in depth is the standard collision — and the resolution is engineering, not derivation.
- Complete mediation is expensive at scale. Checking every access on every path costs latency, and the real designs trade a bounded staleness window for throughput. Name the window.
- Least privilege has a granularity limit. Privileges must be expressible before they can be minimised, and most systems’ permission vocabularies are far coarser than the actual need.
- Psychological acceptability resists measurement. It is the principle most often invoked to justify weakening a control, and there is no test that says when that is honest.
- They cover design, not implementation. A perfectly-principled architecture with a buffer overflow in it is still exploited. These prevent whole classes of design defect and zero classes of coding defect.
Fifty years on, the list has needed no additions of substance. That durability is itself the argument for taking it seriously: the mechanisms changed completely and the failures did not.