What Security Engineering Actually Is
Security engineering is not a checklist of vulnerabilities; it is the discipline of deciding what you value, who can reach it, what you are trusting, and what happens when one of those assumptions turns out to be wrong.
Frame the problem
Security starts with a concrete asset, attacker capability and trust crossing.
The six questions
A vulnerability list tells you what other people got wrong. It does not tell you whether *your* system is exposed, how badly, or what to do first. Security engineering starts one level up, with a sequence of questions that works on any system, in any language, at any scale — and that you can answer for a component in about ten minutes.
The order matters. Asking "are we vulnerable to XSS?" before asking "what do we have that is worth stealing?" produces a backlog with no priorities. Asking "what is the blast radius?" before "which control prevents this?" is what turns a finding into a design decision instead of a ticket.
Notice that two of the six questions are not about prevention at all. Detection and failure-of-control are where real systems live: every prevention you deploy will eventually be bypassed, misconfigured, or shipped around by a deadline, and the question that separates a security engineer from a checklist is *what happens then*.
- What is the asset? Name the specific thing: the
userstable, the signing key, the ability to issue refunds. "The system" is not an asset. - Who is the attacker? An unauthenticated stranger, a logged-in customer, another tenant, a compromised worker, an insider, a malicious dependency. Each has a different starting position.
- What capability does the attacker have? Can they send HTTP requests? Run code in a browser you serve? Read your build logs? Capability, not intent, decides what is reachable.
- What trust boundary is crossed? The interesting bugs are always at a boundary: browser → API, API → database, CI → production, model → tool.
- What is the blast radius? One user, one tenant, every tenant, the whole company. This is the number that decides priority.
- Which control prevents it, which control detects it, and what happens if that control fails? Three questions that must be answered together, because a system with only prevention is a system that fails silently.
Prevention, detection, recovery
Controls come in three kinds and teams routinely buy only the first. Prevention stops the action: a parameterized query, an authorization check, a network rule. Detection notices that it happened: an alert on a spike of 403s, an audit log entry, an anomaly in egress volume. Recovery limits the duration: credential rotation, session revocation, a restore from backup, a rollback.
The reason all three are needed is arithmetic. Prevention has some failure rate — a missed check, a new endpoint, a dependency upgrade that changes escaping behaviour. If detection is absent, the expected time an attacker spends inside your system is not bounded by anything. Most catastrophic breaches are not remarkable exploits; they are ordinary exploits that nobody noticed for eight months.
This is also why "we fixed it" is never the end of an incident. The fix is prevention. The questions that follow — *would we have seen it? how long would it have taken? what would we have had to rotate?* — are what turn one incident into a system that survives the next one.
| Threat | Prevent | Detect | Recover |
|---|---|---|---|
| Stolen session token | HttpOnly + Secure cookies, short lifetime, rotation on privilege change | Same session ID from two countries within minutes; sudden burst of sensitive reads | Revoke the session server-side; force re-authentication; notify the user |
| SQL injection | Parameterized queries everywhere; a database role that cannot read other tables | Query errors with syntax fragments; unusual row counts; WAF signal as a *hint*, not a gate | Rotate database credentials; audit what the role could reach; restore from a known-good backup |
| Leaked API key in git | Secret scanning in pre-commit and CI; secret manager instead of files | Provider-side alerts on unusual usage; scanner hits on public repos | Revoke first, investigate second; rotate everything the key could reach |
| Over-privileged worker compromised | Scoped machine identity; egress allowlist | Worker calls an API it has never called before; new outbound destination | Kill the workload, rotate its credentials, review what it touched via audit logs |
Never say "secure"
"Is it secure?" has no answer, because security is not a property a system has; it is a relationship between a system, a set of assets, and a set of attackers with capabilities. A system can be entirely adequate against opportunistic scanning and entirely inadequate against a determined insider, and both statements are true at once.
The honest replacement is a four-part answer that a reviewer can actually act on: identified controls (what we have), known risks (what we have decided to accept, with the reason), residual risks (what survives the controls we deployed), and unknowns (what we have not examined yet). This framing survives contact with reality; "we are secure" does not survive the first pentest.
It also fixes the incentive problem. A team that must declare "secure" learns to avoid looking. A team that maintains a residual-risk list is rewarded for finding things, which is the behaviour you actually want. See Risk, Residual Risk and Honest Reporting for how to keep that list from becoming a graveyard.
Key points
- Start from the asset, not the vulnerability. A vulnerability list without assets produces a backlog with no priorities.
- Capability, not intent, decides what an attacker can reach. Model what they can *do* from where they already stand.
- Every control belongs to one of three families — prevention, detection, recovery — and a system with only prevention fails silently.
- Blast radius is the number that sets priority: one user, one tenant, all tenants, the company.
- Replace "is it secure?" with identified controls, known risks, residual risks and unknowns.
Boundary control exercise
This lesson uses the shared boundary-control exercise.
Follow the attack
Safe conceptual simulation: capability → missing control → crossed boundary → asset impact.
- 1Attacker → reachable surface: enumerate what answers on the public internet — endpoints, subdomains, a forgotten admin panel, an object store left readable.
- 2Reachable surface → weakest crossing: pick the boundary that validates least. Usually the newest endpoint or the one written under deadline.
- 3Crossing → capability: turn "I can send a request" into "I can run a query / read a file / call an internal service".
- 4Capability → asset: use that capability to reach the thing with value — data, money, or the ability to grant themselves more access.
- 5Asset → persistence: create a second way in (a new API key, an added OAuth app, a modified CI workflow) so that fixing the first bug changes nothing.
- Confidentiality: data that was supposed to be readable by one person becomes readable by anyone.
- Integrity: records are modified with no reliable way to tell which ones, which turns a security incident into a data-quality incident that lasts months.
- Availability: the service stops answering, which is a security failure even when nothing was stolen.
- Trust and obligation: notification duties, contractual consequences, and the slower cost that every future security request now gets funded reactively.
Defend, detect, recover
One prevention is a single point of security failure. Layer it and make failure observable.
- • Write down the assets before writing the controls; a control with no named asset is a guess.
- • Enumerate trust boundaries explicitly in the architecture diagram, and require every crossing to name what it validates.
- • Default to deny: new endpoints, new buckets, new roles start closed and are opened deliberately.
- • Reduce what has to be trusted rather than trying to make more things trustworthy — the smallest trusted computing base wins.
- • Audit-log every privileged operation with who, what, which resource, when, from where and the result.
- • Alert on control *absence*, not only on attacks: an endpoint deployed without an authorization decorator, a bucket that turned public, a role that gained a wildcard.
- • Track authentication and authorization failures as a rate, per identity and per resource — a slow climb is more informative than a spike.
- • Contain before you investigate: revoke the credential, kill the session, isolate the workload. Evidence is worth less than stopping the bleeding.
- • Assume everything the compromised identity could reach was reached, until logs prove otherwise.
- • Rotate transitively: the leaked key, everything it could read, and anything that was itself a credential.
- • Write the timeline while it is fresh, and turn the missing detection into a specific alert before closing the incident.
- • You cannot enumerate every asset; systems accrete data stores, and the forgotten ones are the ones nobody protects.
- • Threat models age. The model is accurate on the day it is written and drifts with every deploy.
- • An insider with legitimate access defeats most boundary controls by definition; the remaining defenses are least privilege, audit and separation of duties.
- • Supply chain compromise starts inside your trust boundary, which is exactly where the model assumed safety.