Learn Security Engineering
Start with assets and capabilities, trace data and identity across trust boundaries, run the safe attack path, then design prevention, detection, recovery and a residual-risk statement.
Assets, threats, trust boundaries, blast radius, least privilege, defense in depth — and why no system is ever called "secure".
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.
Three questions that classify any security failure: who can read it, who can change it, and can legitimate users still work — plus the three that modern systems need alongside them: authenticity, accountability and privacy.
A trust boundary is any point where data or control crosses from something you do not control into something you do — and every one of them is a place where an assumption must be re-validated rather than inherited.
The attack surface is the set of places an attacker can send input or trigger behaviour — and reducing it is usually cheaper, more durable and more measurable than defending every entry you leave open.
Give every identity — human, service, job or agent — exactly the permissions its job requires, because the permissions you grant are the definition of how bad a compromise of that identity can be.
Design so that no single control failing is enough to lose the asset — because every control will eventually fail, and the question that matters is what the next layer does about it.
The default configuration is the configuration most of your system will actually run, so the security question is not "can it be configured safely?" but "what happens when nobody configures it at all?"
When the control cannot make a decision — the policy service is down, the token cannot be verified, the rate limiter is unreachable — the system must do something, and choosing which way it fails is a design decision with no universally right answer.
Security is risk management, so the output of security work is not "secure" but four lists: identified controls, known risks we accepted, residual risks that survive our controls, and unknowns we have not examined.
System → assets → actors → entry points → trust boundaries → threats → mitigations. STRIDE, attack surface mapping, attack trees, and how to run a security review on a real architecture.
System → assets → actors → entry points → trust boundaries → threats → mitigations: a repeatable hour of structured thinking that finds design flaws no scanner will ever find, because they are not bugs.
Six prompts — spoofing, tampering, repudiation, information disclosure, denial of service, elevation of privilege — applied to each element of a diagram, which converts "think of what could go wrong" into a finite, checkable list.
Start from the attacker's goal and decompose it into every path that achieves it, so you can see which defense covers several branches at once and which branch nothing covers at all.
Take an architecture diagram and colour every node by exposure — public, authenticated, internal, privileged, highly sensitive — because the pattern that appears is usually more informative than any individual finding.
A repeatable interrogation you can run against any system diagram — yours, a candidate's, a vendor's — that produces specific findings in under an hour without needing to know the codebase.
Who are you? Credentials, password storage, credential stuffing, MFA, passkeys — and one login followed all the way from a browser to a database row.
Authentication answers "who are you?"; authorization answers "what are you allowed to do?" — and the overwhelming majority of real access-control bugs are systems that did the first one correctly and skipped the second.
Authentication is not a login form: it is registration, credential storage, login, second factors, session establishment, re-authentication, recovery, device management and revocation — and attackers reliably target the least-defended stage, which is almost always recovery.
One login traced end to end at four zoom levels — browser to TLS to password verification to session to cookie to the authorized read — naming at every stage what is sent, what boundary is crossed, what must be protected, and what an attacker would try.
The database should never contain the password, and it should not contain a fast hash of the password either — because the entire threat model is what an attacker does with a copy of the table.
Offline guessing against a stolen table, online credential stuffing with passwords from other breaches, and targeted attempts against one account — three different attacks with three different defenses, only one of which is the hash function.
Requiring a second, independent kind of evidence — something you have or something you are, alongside something you know — with the honest ranking of which factors resist phishing and which merely resist password reuse.
Public-key authentication in the browser: the device keeps a private key, the server stores only the public key, and the signature is bound to the origin — which removes the shared secret and makes relayed credentials worthless.
How the server keeps remembering you: session IDs, cookie attributes, hijacking, rotation, revocation, and what JWTs actually buy and cost.
Authentication happens once; the session is what makes the next thousand requests work — a random opaque identifier that maps to server-side state you can inspect, expire and revoke.
Six attributes decide whether a cookie is a reasonable place to keep a session or a liability: `Secure`, `HttpOnly`, `SameSite`, `Domain`, `Path` and lifetime — and each one maps to a specific attack.
The attacker does not need the password: possession of the session token is possession of the account, and every defense is either about preventing the token from escaping or about limiting what it is worth once it has.
A signed, self-contained token that lets a service verify a claim without a lookup — which buys statelessness and pays for it with revocation you have to engineer separately.
The specific ways token validation goes wrong — trusting the header's algorithm, skipping issuer and audience, over-long expiry, treating signed as confidential — and the verification routine that closes all of them.
Delegated authorization, the authorization code flow with PKCE, and the identity layer on top — with the "OAuth is not login" distinction taken seriously.
A protocol for letting an application act on a user's behalf against a third-party API without ever holding the user's password — which is a different problem from logging users into your own site.
The flow that keeps tokens off the front channel: the browser carries only a one-time code, which the client exchanges for tokens over a back channel — with PKCE proving that the exchanging party is the one that started the request.
OAuth plus an identity layer: an ID token issued to your client, signed by the provider, containing verified claims about who the user is — which is the piece OAuth deliberately does not provide.
What are you allowed to do? Ownership checks, RBAC, ABAC and policy, broken access control, tenant isolation, and where enforcement must live.
Ownership checks, roles, permissions, attribute policies, scopes and tenant isolation are different tools for the same question — and most real systems need three of them at once, layered in a specific order.
Users get roles, roles get permissions — simple to reason about, easy to audit, and prone to a predictable failure when the role set grows to match every feature.
Decide by evaluating a rule over the requester's attributes, the resource's attributes, the action and the environment — expressive where roles are not, and dangerous when the rules become unreadable.
`GET /invoices/100` works; `GET /invoices/101` also works and it is not yours. Authentication succeeded, authorization was never asked — the most common serious vulnerability in web applications and APIs.
A `tenant_id` column is not isolation. Isolation is that column enforced consistently across queries, caches, queues, storage, search, logs and AI context — every place a copy of the data exists.
Not in the frontend, not only at the gateway, and not as an optional call in each handler — but at the point where the resource is loaded, structured so it cannot be skipped.
Hashing vs encryption vs encoding, symmetric and asymmetric keys, signatures, TLS as a security boundary, and certificate trust chains.
Plaintext, ciphertext, keys, hashes, MACs, signatures, nonces and randomness — the vocabulary you need to use cryptography correctly, plus the one rule that matters most: use established libraries and protocols, never your own.
Encoding is reversible without a secret, hashing is one-way, encryption is reversible with a key — three different tools that are routinely confused, and the confusion produces real vulnerabilities.
One shared key encrypts and decrypts — fast enough for bulk data, and the whole difficulty is getting that key to both parties and keeping it from everyone else.
A public key anyone may hold and a private key only one party holds — enabling key exchange without a shared secret, signatures anyone can verify, and identity that does not require pre-sharing anything.
Sign with a private key, verify with the public key: proof that a message is unmodified and came from the key holder — the mechanism behind JWTs, webhooks, signed artefacts and passkeys.
Networking explains how the handshake works; here the question is what TLS actually guarantees — server authentication, confidentiality and integrity for one hop — and the many things "we use HTTPS" does not cover.
A client accepts a server certificate only when the hostname, validity and signatures form a chain through an intermediate to a locally trusted root.
The browser is an execution environment an attacker can reach. XSS, CSRF, CORS and the origin model that decides what any of them mean.
Map browser execution, origins, cookies, API decisions and database interpretation onto one end-to-end path.
XSS exists when attacker-controlled data crosses into an HTML, attribute, URL or JavaScript context and the browser interprets it as behavior.
Escaping rules depend on whether data enters HTML text, an attribute, a URL or JavaScript; validation alone cannot solve every context.
CSP limits which code a browser may execute, buying a second boundary after an output-encoding defect.
A browser can attach ambient credentials to a request initiated by another site; the server mistakes possession of a cookie for user intent.
Choose defenses from the authentication style: ambient cookies create CSRF exposure; explicit authorization headers change the browser behavior and threat.
CORS is a browser-enforced cross-origin response-reading policy, not server authentication and not protection from non-browser clients.
The same bug in six costumes: data crossing a boundary and being interpreted as instructions — SQL, shell, paths, URLs, serialized objects.
SQL injection is a boundary failure: user data is concatenated into query syntax and the database interprets it as instructions.
A shell is an interpreter: when user data becomes part of a command string, punctuation can become behavior.
Unsafe path construction lets an identifier escape the directory the developer intended as its boundary.
A user-controlled URL turns the backend’s network position and credentials into an attacker-controlled capability.
URL parsing is only one layer; destination policy, DNS/IP checks, redirect handling, egress rules and workload identity constrain the real capability.
Untrusted serialized bytes should become validated data, not native objects with executable hooks and surprising behavior.
Authentication, object-level authorization, rate limits, replay, webhooks, mass assignment and file uploads — the boundary most systems actually expose.
Every API operation must parse, validate, authenticate, authorize, limit and record—especially when a request names a resource.
Validation answers “is this value well-formed and meaningful?”; authorization separately answers “may this principal perform this action?”
Binding an arbitrary request object onto a model lets the caller edit fields the UI never exposed, including roles, owners and prices.
Rate limits make guessing and resource abuse more expensive, but they are not authentication and can be distributed around.
A valid request can still be harmful when captured and repeated; authenticity does not automatically imply freshness or exactly-once effect.
A public webhook endpoint must verify who sent the exact body, when it was sent, and whether the event was already processed.
A file crosses several boundaries—request, parser, storage, scanner, processor and serving path—and each interprets different metadata.
Database privileges, data classification, encryption at rest versus in transit, and the copies of your data you forgot about: backups, replicas, logs.
Database security is authentication, network reachability, least privilege, encryption, audit, tenant enforcement and secure copies—not one checkbox.
An application connected as superuser turns every injection or process compromise into total database compromise.
Classify data as public, internal, confidential or highly sensitive so handling rules follow the value and consequence of exposure.
Transport encryption protects links; storage encryption protects media and some infrastructure paths. Neither decides who is allowed to read the plaintext.
A backup carries the confidentiality, retention and deletion obligations of production—and adds restore integrity and availability concerns.
Segmentation, firewall rules, egress control, process isolation, privilege separation, sandboxing — and why a container is not a security boundary you can lean on.
Network design controls reachability and blast radius; it does not turn an internal caller into a trusted identity.
Internet → public layer → application network → database network → management network: crossings are explicit and narrow.
A rule is source, destination, port, protocol and action; every broad wildcard is an explicit expansion of attack surface.
Security also asks what a compromised service can call outward; unrestricted egress enables SSRF, command-and-control and exfiltration.
Processes, users, file permissions, privileges, patching and resource limits decide what a compromised application can do next.
A web server should run as a restricted user; administrative setup and runtime request handling should not share one authority.
Owner, group and others each receive read, write and execute; the effective service identity determines which boundary actually exists.
A sandbox constrains files, network, CPU, memory, syscalls and credentials so untrusted computation cannot spend or reach everything.
Containers package and isolate processes but share a host kernel; privileged mode, host mounts and broad capabilities erase much of the boundary.
Identity → policy → action → resource. Machine identities, short-lived credentials, and the full lifecycle of a secret from creation to revocation.
Cloud security is identity and policy first, then reachable networks, resource policies, secrets, encryption and evidence.
An IAM decision binds a principal, action, resource and conditions; a wildcard in any dimension expands blast radius.
People, services, CI/CD, agents and automation need separate identities so access can be scoped, attributed and revoked independently.
A workload exchanges its identity for a temporary credential that expires, reducing the useful lifetime of theft and eliminating manual rotation.
A secret should not live everywhere: applications retrieve or receive narrowly scoped values from a controlled system with audit and rotation.
Create → store → distribute → use → rotate → revoke → audit: weakness in any stage determines the effective protection.
A committed .env file or secret baked into an image turns version control and every image copy into credential stores.
Your code is a minority of what you ship. Dependencies, lockfiles, provenance, malicious packages, CI/CD permissions and build integrity.
Your code, dependencies, build tools, CI identities, registries, container bases and deployment artifacts all execute with trust.
Lockfiles, review, scanning, provenance and compatibility tests manage transitive code without treating every update as automatically safe.
A plausible package name can deliver attacker code during install or build; popularity and a familiar-looking name are not provenance.
Pull-request code, build runners, secrets and deployment authority meet in CI; trust must change across fork, branch and environment boundaries.
Build once, identify immutably, record provenance, verify before promotion and never rebuild “the same” release in a more privileged environment.
Secure defaults, fail closed, complete mediation, minimal trusted computing base, error handling that does not leak, and audit logs that answer questions.
Overlay public entry points, identities, authorization, trust boundaries, encrypted links, sensitive stores, privileged services and audit sinks on the ordinary system diagram.
Secure defaults, deny by default, least privilege, complete mediation, separation of duties and explicit boundaries make the safe path the easy path.
Users need a stable safe error; operators need correlated internal diagnostics. Sending the latter to the former reveals implementation and sometimes secrets.
Logs are production data stores: they should contain decision evidence, not passwords, tokens, full payment details or unnecessary personal data.
Who did what to which resource, when, from where, with what result—and which policy allowed it.
Prevention fails. Telemetry → detection → alert → investigation → containment → recovery → learning, plus vulnerability management that prioritises exposure over CVSS.
Telemetry becomes a detection hypothesis, an alert, an investigation and a response path; a noisy alert with no owner is not a control.
New device, unusual location, impossible-travel signals and failed attempts change risk; none proves compromise on its own.
Prepare → detect → contain → eradicate → recover → learn: restoring service without containment or evidence can extend the incident.
Discover → triage → assess exposure → prioritize → fix or mitigate → verify; severity is not risk without reachability, assets and controls.
SAST, DAST, dependency and secret scanning, fuzzing, penetration testing — what each finds, what each misses, and the security tests that belong in CI.
SAST, DAST, dependency and secret scanning, fuzzing, penetration tests and review each see a different slice; no scanner understands every authorization rule.
Generate structured input variations and watch for crashes, hangs, invariant violations and resource blowups—especially at parsers and file boundaries.
User A cannot access User B’s invoice; expired tokens and unsigned webhooks are rejected; non-admins cannot call admin operations.
A fixed security bug becomes a permanent executable invariant using the smallest test that reproduces the original boundary failure.
Use vulnerability categories as an index—then always follow vulnerability → why it exists → secure design → test.
Prompt injection, untrusted tool output, over-privileged tools, poisoned retrieval and memory — and the one rule: the model is never the authorization layer.
Agents combine untrusted language, retrieved data, memory, models and tools; capability boundaries—not model obedience—control the outcome.
User, model, retrieval, memory, tools and external content have different trust and privilege; mark every flow explicitly.
A user or retrieved document supplies language that the model may confuse with authority; the robust defense is to constrain capabilities and decisions outside it.
A tool schema is a capability interface: make it narrow, bind it to a principal, enforce policy outside the model and record the result.
The agent proposes an action; deterministic code evaluates principal, action, resource and policy. Natural-language confidence is never permission.
Money transfer, deletion, external communication and permission changes should pause at an explicit risk gate with a comprehensible diff.
A website, API or integration can return text that is false or malicious; tool data must not become higher-priority authority simply because a tool fetched it.
Retrieval and memory add durable, searchable copies of data where poisoning, tenant-filter mistakes, retention and sensitive recall become security boundaries.
Code execution gets limited files, network, CPU, memory and scoped credentials; the sandbox must constrain the capability, not just the process tree.