Security by Design, Not by Accident
Security fails when it’s an afterthought bolted onto a finished system. Secure system design makes security a design constraint from the start: every component gets its threat model, its least-privilege posture, and its place in a layered defense. This topic is the capstone of the security track — the lens through which you review any architecture.
Threat Modeling: Think Like the Attacker, First
Threat modeling is the discipline of enumerating who could attack what, how, and why — before the code exists. The standard framework is STRIDE, a mnemonic for the threat categories per component/data flow:
| Threat | What it attacks | Example |
|---|---|---|
| Spoofing | authenticity | impersonating a user/service |
| Tampering | integrity | modifying a request, a file, a JWT |
| Repudiation | non-repudiation | denying a transaction that happened |
| Information disclosure | confidentiality | leaking data in a response/log |
| Denial of service | availability | exhausting a resource |
| Elevation of privilege | authorization | exploiting a bug to become admin |
The workflow:
- Draw the data flow — components, trust boundaries, and where data moves.
- Walk each flow against STRIDE — for every arrow, ask each threat: could this happen here?
- Prioritize — by likelihood × impact (the famous “CIA” priorities: confidentiality/integrity/availability by data type).
- Design mitigations — each accepted threat either gets a control or is explicitly risk-accepted.
A data flow diagram plus a STRIDE walk is the highest-ROI security activity there is — it finds the dangerous paths (password flows, admin endpoints, payment data) before they’re built.
Least Privilege: The Overarching Law
Least privilege says every actor — a user, a service, a process, an IAM role, a container — gets exactly the permissions it needs and nothing more. The idea generalizes across every layer you’ve studied:
- IAM/roles — a service uses a narrowly scoped role (
s3:PutObjecton one bucket), not*:*. - OS/process — containers run as non-root (Docker topic), apps don’t run as admin.
- Databases — the app’s DB user can
SELECT/INSERT/UPDATEits tables, notDROP DATABASE. - Secrets — each service sees only its own secrets, not a shared master key.
The payoff: a compromise is contained. A leaked container with least-privilege can’t destroy the fleet; a leaked admin key can. Least privilege is the structural defense — every other control assumes it.
Defense in Depth: Layers That Don’t Share a Weakness
No single control is perfect; defense in depth stacks independent layers so a bypass at one level is caught by the next:
- Edge: DDoS, WAF, rate limiting (Network Security topic).
- Transport: TLS everywhere, mTLS service-to-service.
- App: input validation, parameterized queries, CSP (AppSec topic).
- Identity: MFA, OAuth2/OIDC, RBAC (Auth topic).
- Data: encryption at rest, key management.
- Monitoring: audit logs, anomaly detection, alerting (Observability topic).
The layers must be independent — two layers that both rely on the same assumption (e.g., “requests are trusted”) are one layer. “We have a WAF” is not a substitute for “we parameterize SQL.”
Secrets Management
A secret is any credential that unlocks something: DB passwords, API keys, TLS private keys, OAuth client secrets, signing keys. The failure modes are famous and avoidable:
- In source control — a committed
.envor hardcoded key. This is a structural bug (history never forgets) — secrets scanning in CI and pre-commit hooks are the guardrail. - In container images — baked into a Docker layer (remember: layers are permanent, so the secret lives in every derived image).
- In logs/config dumps — leaked to aggregators.
The fix is a secrets manager: Vault, AWS Secrets Manager, GCP/Azure equivalents. Secrets are stored encrypted, rotated on schedule, granted by policy (least privilege again: each app gets the secrets it needs), and injected at runtime (env vars, mounted files, or Vault agent) rather than baked into images. Add rotation: a leaked secret only matters if it’s still valid; rotation makes the leak’s shelf life short.
Secure Development Lifecycle
Security is a pipeline stage, not a person:
- Design — threat model, security review of the architecture.
- Develop — secure defaults, least privilege, no secrets in code, code review.
- CI/CD — static analysis (SAST), dependency scanning (SCA — the Log4Shell-style “vulnerable dependency” class), secrets scanning, container scanning.
- Test — dynamic testing (DAST), security test cases for the threat model.
- Deploy/Run — minimal permissions, encryption, audit logging, monitoring, incident readiness.
- Respond — an incident-response plan before the incident: who’s on call, how you contain, how you disclose.
The security review checklist that closes each phase:
- Threat model updated for the changes?
- Least privilege on every new role/service/secret?
- No secrets in code, images, or logs?
- All inputs validated, all queries parameterized, authz on every endpoint?
- TLS everywhere, encryption at rest?
- Audit logs capturing who-did-what?
- Dependencies scanned, pinning in effect?
Incident Response for Security Events
Security incidents follow the same shape as ops incidents (SLO topic) with harder stakes:
- Detect — from audit logs, alerts, or (honestly) a vendor/customer notification.
- Contain — stop the bleed first: revoke the leaked credential, isolate the box, disable the account. Mitigation before forensics.
- Eradicate & recover — remove the attacker’s foothold, restore from trusted backups.
- Analyze — what was accessed, for how long, what data. This is where audit logs pay for themselves.
- Disclose & improve — notify affected parties, run the blameless postmortem, fix the root cause.
The critical operational habit: audit logs are the only way to answer “what did the attacker touch?” — if you don’t log access, the answer is “everything, we think.” Least privilege is what makes containment possible; logs are what make the analysis possible.
Practice Trajectory
- Draw a data flow for a small web app and run a STRIDE walk on the login, payment, and admin paths; list mitigations for each accepted threat.
- Audit an IAM setup: find the
*:*role and rewrite it to least-privilege (which actions on which resources?). - Move a hardcoded secret into a secrets manager, rotate it, and update the app to inject it at runtime.
- Add SAST, SCA, and secrets scanning to a CI pipeline and fix the findings they surface.
- Run a mock security incident: a leaked API key is detected — contain, rotate, audit what it could access, and write the postmortem.
When It’s the Right Tool
| Situation | Takeaway |
|---|---|
| Designing anything that touches data | Threat model it before you build |
| A compromise must not become a breach | Least privilege, everywhere, always |
| One control isn’t enough | Defense in depth with independent layers |
| “Where’s the DB password?” | Secrets manager, injected at runtime, rotated |
| “What did the attacker touch?” | Audit logs, or you can’t know |