Security
What experienced engineers forget about application security before an interview, grouped by subtopic. For AWS IAM and workload credentials, see aws.md; for backend-side input handling, see backend.md.
Server-side attacks
SQL injection
- Untrusted input becomes SQL syntax (SQL injection). Fix with parameterized queries, not hand escaping.
- Identifiers (sort column, table name) can't be bound — allowlist them; ORMs don't protect raw fragments.
Command and template injection
- Command injection: input inside a shell string (
system("convert " + name)) — pass an argument array with no shell.
- SSTI: user input compiled as a template (Jinja2, Twig) runs code on the server — pass it only as template data.
- XXE: XML parsers resolving external entities read local files or make requests — disable DTDs and external entities.
Path traversal and unsafe deserialization
- Path traversal:
../../etc/passwd in a filename escapes the base directory — resolve the canonical path and check it's still inside the base, or map IDs to files.
- Unsafe deserialization (Java serialization, Python
pickle, YAML with object tags) runs attacker-chosen code — only deserialize untrusted input into plain data (JSON) with schema validation.
SSRF
- The attacker controls where the server sends a request — reaching cloud metadata or internal services (SSRF).
- Allowlist destinations, validate the resolved IP, and pin it for the request to avoid DNS rebinding.
- IMDSv2 on AWS requires a session token, blocking naive metadata theft.
Prototype pollution
- Merging untrusted JSON with
__proto__ or constructor.prototype keys adds properties to Object.prototype (prototype pollution) — every object gains them, so checks like if (user.isAdmin) can flip.
- Reject those keys, parse into
Map or Object.create(null), or freeze the prototype.
HTTP request smuggling
- A proxy and the backend disagree on where a request ends (
Content-Length vs Transfer-Encoding), so attacker bytes become the start of the next user's request (request smuggling).
- Reject ambiguous requests at the edge, use HTTP/2 end to end, and keep proxies patched.
Resource-exhaustion attacks
- ReDoS: backtracking regex engines take exponential time on patterns like
(a+)+$ with crafted input; use linear-time engines (RE2, Go regexp, Rust regex) or match timeouts.
- Decompression bombs and XML billion laughs expand kilobytes into gigabytes — cap the decompressed size and disable entity expansion.
- Hash flooding sends colliding keys that make hash tables O(n) per operation; runtimes defend with randomized hash seeds (SipHash).
- Cap JSON nesting depth, GraphQL query depth and cost, and request sizes at the edge.
Browser-side attacks
XSS
- XSS is stored (saved, rendered later) or reflected (echoed in the response); DOM-based means client code turns data into markup or script, and its payload can be stored or reflected too.
- Fix with context-aware output encoding and safe sinks (
textContent, not innerHTML); CSP limits damage but isn't the primary fix.
Content Security Policy
CSRF
CORS
- CORS is a browser read policy, not authentication or a firewall — non-browser clients ignore it.
- A credentialed response can't use
Access-Control-Allow-Origin: *; reflecting any Origin with credentials is equivalent to no policy.
- Simple requests (GET, HEAD, or POST with a form or
text/plain body and no custom headers) go out without a preflight: the server acts on them even though the browser hides the response, so CORS is no CSRF defense.
- Other requests are preflighted with
OPTIONS; Access-Control-Max-Age caches the result.
Open redirect
/login?next=https://evil.example sends victims to a phishing page through a link on your domain, and can leak OAuth codes (open redirect).
- Resolve the target against your origin and require the result to stay same-origin (
//evil.example is a relative network-path reference to another host), or check it against an allowlist of hosts.
Authentication and sessions
Password storage
- Use a slow, memory-hard hash — argon2id, else scrypt — never a fast hash like SHA-256; store algorithm and parameters with the hash (OWASP).
- bcrypt is for legacy systems only: not memory-hard, and input is capped at 72 bytes (some libraries truncate silently, others reject — Python
bcrypt 5.0+).
- A unique salt per password defeats rainbow tables; a pepper (server-side secret stored elsewhere) helps if only the database leaks.
Password policy (NIST SP 800-63B)
- Rev. 4 (2025): minimum 15 characters for password-only login (8 with MFA), allow at least 64, no composition rules, no forced periodic rotation.
- Check new passwords against breached-password lists (Pwned Passwords); rotate only on evidence of compromise.
Credential stuffing and enumeration
- Credential stuffing replays leaked username/password pairs: rate-limit per account and per IP, add MFA, and check logins against breached-password lists.
- Return the same error and similar timing for "no such user" and "wrong password" to avoid account enumeration.
MFA strength
- Phishing resistance: WebAuthn/passkeys > TOTP app > SMS (SIM swap).
- A passkey's private key never reaches the server (synced passkeys copy it end-to-end encrypted between the user's devices), and its signature covers the origin, so a lookalike domain gets nothing usable.
Cookie flags
Session fixation
- An attacker plants a known session ID before login, then shares the victim's session once they authenticate (session fixation).
- Always issue a new session ID on authentication and privilege change.
Password reset and recovery
- Reset tokens are single-use, short-lived, high-entropy, and stored hashed like passwords (OWASP).
- Respond identically whether or not the account exists; revoke other sessions after a reset.
- Build the reset link from a configured origin, never the request's
Host header — host-header poisoning sends the token to the attacker's domain.
- Recovery is often the weakest login path (SIM swap, support-desk social engineering): hold it to the same assurance as MFA.
Browser token storage
HttpOnly cookie: safe from XSS token theft, needs SameSite + CSRF tokens — the default for web apps.
- In memory: no CSRF, gone on refresh (needs silent refresh), still usable by a script running on the page.
localStorage: any XSS reads it directly — avoid for sensitive tokens.
Tokens and federation
JWT validation
- Pin the expected
alg, reject none, and block RS/HS confusion (an RS256 token re-signed as HS256 with the public key as the secret).
- Pick the key by
kid from a trusted set (JWKS); validate exp, aud, and iss — a valid signature is not authorization.
JWT revocation
- Stateless JWTs can't be revoked in place — use short expiry plus refresh-token rotation.
- A denylist of token IDs (
jti) works but brings back a server-side lookup on every request.
OAuth 2.0 vs OIDC
- OAuth 2.0 is delegated authorization (an access token for a resource server); OIDC adds authentication via an ID token.
- Interactive login: authorization code + PKCE; service to service: client credentials. OAuth 2.1 drops the implicit and password grants.
OAuth pitfalls
- Match
redirect_uri exactly against a registered value — prefix or wildcard matching leaks authorization codes.
- The
state parameter (or PKCE) ties the callback to the browser session that started it, blocking login CSRF.
- Bearer tokens work for whoever holds them; DPoP or mTLS sender-constrains tokens to a client key.
Refresh token rotation
- Each refresh returns a new refresh token and invalidates the old one (RFC 9700).
- Reuse of an old token signals theft, so revoke the whole token family.
Authorization
RBAC, ABAC, ReBAC
| Model |
Decision based on |
Fits |
Weak spot |
| RBAC |
roles |
few, stable roles |
role explosion from exceptions |
| ABAC |
subject/resource/context attributes |
fine-grained conditions |
needs trustworthy attributes |
| ReBAC |
relationship graph ("member of team that owns") |
nested sharing (Google Docs, Zanzibar) |
graph storage and evaluation cost |
- Enforce server-side and deny by default — a hidden button is not access control.
IDOR / BOLA
- The endpoint checks that the caller is logged in but not that they may access this object ID (BOLA).
- Authorize per resource on every request; unguessable IDs only hide the bug (IDOR).
Cryptography
Secure randomness
AES-GCM nonces
- AES-GCM is authenticated encryption: confidentiality plus an integrity tag.
- Reusing a nonce with the same key breaks both — nonces must never repeat per key; random 96-bit nonces are safe for about 2³² messages per key (NIST SP 800-38D).
RSA vs ECC and forward secrecy
- ECC gives equal security at far smaller keys (256-bit EC ≈ 3072-bit RSA, NIST SP 800-57), making signatures and handshakes cheaper.
- ECDHE gives forward secrecy: a later leak of the long-term key doesn't expose past sessions.
MAC vs signature
Envelope encryption and key management
- Encrypt data with a random data key (DEK), encrypt the DEK with a key-encryption key (KEK) held in a KMS/HSM, store the wrapped DEK with the data (envelope encryption).
- Rotating the KEK never re-encrypts the data: KMS keeps old key versions to unwrap existing DEKs, and retiring one only means rewrapping DEKs; the master key never leaves the KMS. AWS specifics: see aws.md.
Timing attacks
TLS
TLS 1.3 handshake
- TLS 1.3 does a 1-RTT full handshake: key shares go in the first flight.
- Optional 0-RTT resumption data can be replayed — accept it only for requests safe to repeat (idempotent and cheap); HTTP servers can answer
425 Too Early.
Post-quantum key exchange
- Harvest now, decrypt later makes key exchange the urgent part: browsers and CDNs negotiate hybrid X25519MLKEM768 (classical + ML-KEM, FIPS 203) by default since 2024–2025.
- Post-quantum signatures (ML-DSA, FIPS 204) are slower to roll out because certificates and chains grow much larger.
Certificates and mTLS
- The client validates the chain to a trusted root and the hostname; the server proves key ownership by signing the handshake.
- mTLS: both sides present certificates — common for service-to-service auth in a mesh.
HSTS and pinning
- HSTS forces HTTPS on later visits after one HTTPS response (preload lists cover the first visit).
- Certificate pinning resists CA compromise but makes key rotation painful — mostly limited to mobile apps now.
Secure development
Dependency attacks
- Typosquatting (a lookalike package name) and dependency confusion (a public package shadowing an internal name) trick installers.
- Pin dependencies with lock files and hashes; install scripts run arbitrary code, so disable them where possible.
Provenance and signing
- An SBOM (SPDX, CycloneDX) lists what's inside an artifact, so a new CVE can be matched to affected builds.
- SLSA levels grade build provenance; Sigstore (cosign) signs artifacts with short-lived keys tied to a CI identity.
STRIDE
| STRIDE threat |
Violates |
| Spoofing |
Authentication |
| Tampering |
Integrity |
| Repudiation |
Non-repudiation |
| Information disclosure |
Confidentiality |
| Denial of service |
Availability |
| Elevation of privilege |
Authorization |
- STRIDE finds threats at each trust boundary; it doesn't rank them — pair it with impact/likelihood.