The Security Mindset
The same inversion this lesson tells in words, drawn once: an ordinary system diagram, redrawn with every door an attacker could use marked on it instead of every feature it offers.
Before you start. You need two things from earlier in this site: that a program is just instructions a machine runs one at a time (How Code Becomes Execution (compilers, stack & heap)), and that two things sharing one machine can step on each other’s memory unless something actively stops them (Concurrency & the Hardware). Nothing else - this is the first lesson in the security series, so no earlier security lesson is assumed.
1967: a machine that already kept one secret
A defense contractor in St. Louis owned an IBM mainframe running classified work for a fighter jet program. Wanting extra income, it asked the Pentagon to let it sell the machine’s spare hours to local businesses, who’d connect over remote terminals while the classified job kept running underneath.
The Department of Defense had no answer ready: until then, everyone who touched a computer was already cleared for whatever was on it, and now a business that had never seen a classified file would share the same memory and disks as one. The Pentagon’s research arm asked Willis Ware, of the defence think tank RAND, to chair a committee and work out how to let both users on the machine without the classified one leaking to the other.
Ware’s committee spent about three years on it. Their report, delivered in January 1970 and known since as the Ware report, is what historians call the start of computer security as its own field.
The question with no name yet: a threat model
Notice what the committee had to do that nobody had done before. An engineer building the shared machine asks “how do I let two users use this at once?” The committee had to ask the opposite question about that same machine: “how does the classified one leak to the other?” Same diagram, opposite question.
That second question, written down as a checked list instead of asked once and forgotten, is a threat model: a written list of every way a system could be made to do something it shouldn’t, checked before the system ships rather than after someone finds the way in. It is the one habit this whole lesson teaches, and everything below is what it looks like in practice.
The doors you didn’t mean to open: attack surface and trust boundary
Draw the shared machine as a diagram: the classified job in one box, the new outside business in another, both touching the same mainframe. Every point where something from outside can reach in is a door - a place a request, a file, or a person enters. Engineers call the full set of doors the attack surface, and the line each one crosses - from a stranger’s terminal into the shared machine - the trust boundary. Ware’s answer: assume a boundary exists whether or not anyone built one on purpose.
Here is that same walk done for an ordinary web system instead of a 1967 mainframe. Each dashed box trusts itself; every arrow leaving one is a door, crossing a boundary:
graph LR
subgraph internet [internet: trusts nothing]
B[browser]
A[attacker script]
end
subgraph edge [edge: public, hardened]
LB[load balancer plus web filter]
end
subgraph app [app network: private]
API[API server]
W[background worker]
end
subgraph data [data: crown jewels]
DB[(database)]
S[(object storage)]
end
subgraph third [third parties: trust that is not yours]
P[payment provider]
end
B --> LB
A --> LB
LB --> API
API --> DB
API --> S
API --> W
W --> DB
API --> P
P -.->|webhook| LB
Count the crossings: nine arrows, but the browser and the attacker cross the same line, so eight. The last is the one people forget, the webhook: the provider calling you back, from outside into the edge, and looks safe because you chose that provider. It isn’t: anyone who can reach that address can fake that message, so it needs the same lock as every other door.
A name for the six ways in: STRIDE
Ware’s committee had to invent its own list of ways a shared machine could leak, because nobody had one yet. Today’s engineers use a ready-made version: a six-letter checklist called STRIDE, walked at every door on a diagram like the one above. Two of its six letters, in plain words: someone at a door claiming to be a user they aren’t, and someone flooding a door with so many requests the real users can’t get through. The other four are in the second read, with the full table.
One lock is a promise, two is defense in depth
Here is why one locked door is never enough, in one number. Say a door is locked correctly 99 times out of 100 - a genuinely good rate. A system with 100 such independent doors is fully locked, all at once, only about 37 times out of 100: multiply 0.99 by itself 100 times and you land at 36.6%. The attacker doesn’t need all 100 to fail - just one.
The fix is not a better single lock. It’s a second, independent lock behind the first, so a single miss doesn’t reach anything - engineers call this defense in depth. Two locks at 99% turn one door’s miss rate from 1% down to 0.01%, since both now have to fail the same day. Depth is not paranoia - it’s the only way to move the odds enough that a hundred doors stops being a coin flip.
That’s the whole habit: assume a door exists wherever something crosses in, walk the checklist at every one, and never trust a single lock with everything behind it. The next lesson, Cryptography Foundations, is the toolkit that builds those locks.
You now know
- A threat model is a written list of every way a system could be made to misbehave, made before it ships, not after someone finds the way in.
- Every point where something from outside reaches a system is a door; the whole set of doors is the attack surface, and the line each one crosses is a trust boundary.
- STRIDE is a six-letter checklist walked at every door, covering things like a faked identity and a flood of requests that locks real users out.
- One lock at 99% still fails about once every hundred tries, so a system with a hundred such doors is fully locked only about a third of the time.
- Defense in depth means a second, independent lock behind the first, so one miss doesn’t reach anything on its own.
- The attacker needs one door; the defender must hold every one, which is why security has to be designed in, not patched on afterward.
Stop here if you want. What follows is the same checklist, the same locks, and the same arithmetic, written out in full.
Second read: the checklist and the arithmetic in full
The full six-letter checklist, kept whole - each letter is a threat, the property it breaks, and its usual fix:
S Spoofing (fake identity) → violates authenticity → AUTHENTICATION
T Tampering (alter data) → violates integrity → hashing / signing
R Repudiation (deny an action) → violates non-repudiation → logging / signatures
I Information disclosure (leak) → violates confidentiality → ENCRYPTION
D Denial of service → violates availability → rate-limit / redundancy
E Elevation of privilege → violates authorization → least privilege / authz
Walk it at every trust boundary: draw the data-flow, mark where zones meet, ask which letters apply, fix the worst first - at design time, since a whiteboard threat costs nothing and a production one costs a breach. The story’s diagram is 48 letter-boundary pairs (eight crossings × six letters), most a fast “no”; the few that aren’t are the backlog. The webhook needs the walk too: it’s spoofing carrying tampered data, fixed with a verified signature.
STRIDE’s letters protect the three properties the field is built around, the CIA triad: confidentiality (only the right people read; breach = a leak), integrity (data stays unaltered; breach = tampering), availability (system stays up for real users; breach = an outage). They trade off - more checks slow things down, more sharing leaks more - so security means the right balance, not maximum lockdown. Two more properties sometimes join them: authenticity and non-repudiation (proving who sent it, and that they can’t deny it).
The rest of the canon, one line each: least privilege, fail securely (an error locks, not opens), complete mediation (check every access, every time), secure defaults, and Kerckhoffs’s principle - no obscurity, since a hidden design leaks eventually, so security rests on a rotatable key, not a hidden mechanism. Defense in depth is the same rings idea as the kernel/user boundary (Processes, Threads & the Kernel): assume one layer breaks, and design so it can’t reach the crown jewels.
The arithmetic in full. p^N is the defender’s clean-sheet chance across N doors each held at rate p, because the attacker only needs the one that isn’t:
// holes.mjs - node holes.mjs
// The asymmetry as arithmetic: if each of N entry points is defended correctly with probability p, what is P(all of them)?
for (const p of [0.99, 0.999]) {
console.log(`p = ${p}: ` + [10, 50, 100, 373, 1000].map(n => `N=${n} -> ${(100 * p ** n).toFixed(1)}%`).join(" "));
}
p = 0.99: N=10 -> 90.4% N=50 -> 60.5% N=100 -> 36.6% N=373 -> 2.4% N=1000 -> 0.0%
p = 0.999: N=10 -> 99.0% N=50 -> 95.1% N=100 -> 90.5% N=373 -> 68.9% N=1000 -> 36.8%
373 is real: this site’s build pulls in that many packages (npm ls --all --parseable | wc -l), each somebody else’s code running as you - a 2.4% clean sheet at p = 0.99. Two independent layers at 0.99 cut that door’s miss rate from 1% to 0.01%, taking 2.4% to 96.3%: cutting N and raising p are the only two levers.
One shortcut for the script: near p = 1, p^N ≈ e^(−N(1 − p)), since a small miss rate compounds like decay:
0.99^100 ≈ e^(−100 × 0.01) = e^(−1) = 0.37 (exact 36.6%)
0.99^373 ≈ e^(−373 × 0.01) = e^(−3.73) = 0.024 (exact 2.4%)
0.999^1000 ≈ e^(−1000 × 0.001) = e^(−1) = 0.37 (exact 36.8%)
0.9999^373 ≈ e^(−373 × 0.0001) = e^(−0.037) = 0.963 (exact 96.3%)
The shape to carry: the clean-sheet chance falls to about a third once N × (miss rate) reaches 1 - a hundred doors at 1%, or a thousand at 0.1%, are the same coin flip.
Try it
Pick one small system you know - your own project’s login, or this site’s own sync process - and do a 15-minute threat model on paper:
- Draw the data flow: client → API → DB, plus any third parties.
- Mark the trust boundaries.
- At each boundary, walk STRIDE - one concrete threat per letter that applies (e.g. S: “someone replays a stolen session token”; E: “a normal user calls an admin endpoint”).
- Note the existing mitigation or the gap. The gaps are your backlog; the same walk scales to a real app, and A shop built to be broken (OWASP Juice Shop practice), later in this track, hands you one built to be attacked.
Then three numbers about your own machine and project, five minutes:
node holes.mjs(the script above); changepto what you honestly think your per-door rate is and read your N off the next line.npm ls --all --parseable | wc -lin any node project (orgo list -m all | wc -l,pip freeze | wc -l): that is your N from dependencies alone, every one of them running as you.lsof -nP -iTCP -sTCP:LISTENlists every port on this machine currently waiting for a connection. Each line is a door; for each, say who is meant to connect to it and whether anything checks.
Go deeper
- OWASP - Threat Modeling & the STRIDE process
- Microsoft - Threat Modeling / STRIDE (the origin)
- Saltzer & Schroeder - “The Protection of Information in Computer Systems” (the 1975 paper that named least privilege, complete mediation, fail-safe defaults, and open design)
- Bruce Schneier - “The Security Mindset”
- OWASP Top 10 (previewing Web Vulnerabilities (the OWASP classics)).
Related
- Cryptography Foundations - the toolkit that delivers each letter of the CIA triad.
- Web Vulnerabilities (the OWASP classics) - the four classic ways input crosses a boundary unvalidated, with the mechanism of each.
- Application Hardening - the supply-chain half of N, and the secure defaults that raise p.
- Anatomy of a Breach - what the one missed hole looks like from the other side, end to end.
- The Sandbox Architecture - DevOps: when a boundary has to hold against code you do not trust, the ladder of isolation.
- The full Security list is in CURRICULUM.
Retrieval practice
9 questions. Answer each one before you open it.
What is the CIA triad, and give the defense and the breach type for each?
Confidentiality (only the right people can read; encryption/access control; breach = leak), Integrity (data unaltered; hashing/signatures; breach = tampering), Availability (system reachable for legitimate users; redundancy/rate-limiting; breach = outage). They define what “secure” means for a system, and they trade off against each other.
What does each letter of STRIDE mean, and which property does it violate?
Spoofing → authenticity (authentication); Tampering → integrity (hashing/signing); Repudiation → non-repudiation (logging/signatures); Information disclosure → confidentiality (encryption); Denial of service → availability (rate-limit/redundancy); Elevation of privilege → authorization (least privilege). It’s the checklist for the walk Ware’s committee had to invent from scratch, at every trust boundary.
What is a trust boundary, and why does it matter more than checking any one input field?
A trust boundary is the line where data moves from a less-trusted zone into a more-trusted one - a stranger’s terminal into the shared machine, the internet into a server. It’s the natural place to concentrate validation, authentication and authorization, since everything crossing it is potentially hostile. Thinking in boundaries, not field by field, is what makes a threat model systematic and catches whole classes of bugs at once.
Explain defense in depth and “assume breach” - how do they relate?
Defense in depth layers independent controls (firewall, auth, DB permissions, encryption, monitoring) so no single failure is catastrophic. “Assume breach” is the stance that one layer will be compromised, so the system is built so a breach of the outer layer can’t reach the crown jewels - a foothold isn’t game over. Depth is the mechanism; assume-breach is the mindset behind it.
Why is “no security through obscurity” (Kerckhoffs’s principle) a core rule?
Because hidden mechanisms inevitably leak - reverse engineering, insider knowledge, disclosure - and a system whose safety depends on secret design collapses the moment that secret is known. Kerckhoffs’s principle says it should stay secure even if everything but the key is public, so security rests on a rotatable key, not on a mechanism you hope stays hidden.
State the attacker/defender asymmetry and its practical consequence.
The defender must secure every possible door; the attacker only needs one that works. You can’t win by patching reactively - shrink the attack surface, walk a checklist at every trust boundary, and layer independent defenses so one miss isn’t fatal. It’s why security has to be designed in, not sprinkled on afterward.
With 100 doors each locked correctly 99% of the time, what is the chance the whole system is clean, and what are the two levers that change it?
0.99^100 = 36.6%, about one in three. The two levers are the two terms of the product: reduce N (fewer endpoints, ports, flags and dependencies) and raise p past what a single control can reach by layering independent controls - two layers at 0.99 give 0.9999 for that hole, which takes 373 entry points from a 2.4% clean sheet to 96.3%.
In the trust-boundary diagram, why is the payment provider’s webhook a boundary crossing even though you chose the provider?
It’s a request arriving from outside your boundary, and choosing the sender doesn’t authenticate the request: anyone who can reach the webhook address can send one shaped like the real one. It’s a spoofing threat carrying tampered data, needing the same treatment as a browser request - verify the signature, validate the body, deny by default.
A threat model on the story’s diagram produces 48 boundary-letter pairs and 40 of them are “not applicable”. Was the exercise a waste?
No - the eight that remain are the backlog, and the 40 quick “no”s are what make the eight trustworthy: each was reached by walking every boundary systematically, not by remembering the threats that came to mind. Ad-hoc listing finds the memorable threats; the checklist finds the complete ones.