Skip to content

Systems & architecture

Web application security

Start with threat modeling, then work through XSS and CSP, CORS and cross-origin isolation, OAuth/OIDC, JWT, the OWASP API Top 10, SSRF and file uploads, secrets management and the npm supply chain — each layer of defense for a real app.

9 parts168 min readUpdated Oct 31, 2026

Parts

  1. Part 1

    Threat Modeling for Product Teams: STRIDE, Data Flow Diagrams and Trust Boundaries, from a 60-Minute Session to Tickets

    How I run practical threat modeling with a product team: the four questions, a data flow diagram with trust boundaries, STRIDE per element on a real system with an SPA, API, Postgres, S3 uploads, a payment webhook and an admin panel, simple risk rating, threats turned into tickets, and a threat model that lives in the repo.

    23 min

  2. Part 2

    Practical Frontend Security: XSS in React, Nonce-Based CSP, Trusted Types, CSRF and Clickjacking

    React's escaping does a lot for you, but not everything. This post walks through the XSS holes React doesn't cover, building a strict nonce-based CSP in Next.js, Trusted Types, CSRF in the SameSite era, where to store tokens, and how to stop clickjacking.

    15 min

  3. Part 3

    CORS and cross-origin isolation in depth: origin vs site, preflights, credentials, CORP, COEP, COOP and Fetch Metadata

    The browser's cross-origin model from the ground up: what the Same-Origin Policy blocks, what CORS relaxes and what it never protects, preflights and credentials, Vary: Origin caching bugs, dangerous allowlists, then CORP, COEP, COOP, Fetch Metadata and SameSite — with NestJS and Next.js configs done right.

    18 min

  4. Part 4

    OAuth 2.0 and OpenID Connect for Web Apps in 2026: Authorization Code + PKCE, BFF and the Real Vulnerabilities

    Every HTTP request of the authorization code + PKCE flow, what state, nonce, ID tokens and access tokens each protect, why the implicit flow is dead, and how to build a Backend-for-Frontend in Next.js so tokens never reach the browser — plus refresh rotation, logout and the bugs I've actually seen.

    21 min

  5. Part 5

    JWT in Depth: Verification Pitfalls, the Classic Attacks, and Key Rotation with JWKS

    How JWS differs from JWE, the claims you must check, alg: none, RS256/HS256 confusion, kid injection, jku abuse — and how to pin algorithms with jose, rotate JWKS keys without logging anyone out, revoke tokens, and test your verifier with the malicious tokens attackers will actually send.

    18 min

  6. Part 6

    The OWASP API Security Top 10 in NestJS: vulnerable code, fixed code, and the tests that keep it fixed

    All ten risks of the OWASP API Security Top 10 (2023) worked through in NestJS: ownership checks in scoped repositories, whitelisted DTOs, response shaping, CASL policies, cost-based rate limits, idempotent business flows, SSRF-safe fetch, and an authorization matrix in e2e tests.

    17 min

  7. Part 7

    SSRF and File Upload Security: URL Fetching and Uploads That Don't Hand Over Your Network

    Link previews, webhooks, image import and file uploads look like boring features, yet they keep showing up in incident reports. Why SSRF blocklists fail, a safe fetch in Node with undici and pinned IPs, IMDSv2, and an upload pipeline: presigned S3, magic bytes, sharp, a separate domain and async scanning.

    18 min

  8. Part 8

    Secrets Management for Small Teams: From .env Files to GitHub Actions OIDC and Short-Lived Credentials

    What counts as a secret, how .env files and long-lived CI tokens actually leak, what to do in the first hour after a leak, secret stores compared, GitHub Actions OIDC to AWS and GCP instead of static keys, rotation without downtime, and typed env validation in a Turborepo monorepo.

    19 min

  9. Part 9

    npm supply chain attacks after Shai-Hulud: what changed and how to harden a JavaScript monorepo

    From Nx s1ngularity and chalk/debug to Shai-Hulud, TanStack and ChainDrop: what the npm attacks actually did, what npm and GitHub changed, and a layered defense — install, CI, publish — applied directly to a Bun + Turborepo monorepo.

    19 min

Series review

0/18 answered · 0 correct

18 questions drawn from the parts of this series. Answers here count toward each post's own quiz.

  1. Question 1 of 18StarterWhat is the main difference between threat modeling and a pentest?
  2. Question 2 of 18IntermediateA worker runs inside the company VPC and processes files that outside users upload to S3. The team says it is "safe because it is on the internal network". Which statement is correct?
  3. Question 3 of 18IntermediateReact escapes every value rendered as text. Why is XSS still a risk in a React app?
  4. Question 4 of 18AdvancedWhy does the post recommend a nonce-based strict CSP with 'strict-dynamic' instead of a domain allow-list?
  5. Question 5 of 18IntermediateA colleague says "our API has strict CORS so we don't need to worry about CSRF". Why is this wrong?
  6. Question 6 of 18IntermediateWhy is setting Access-Control-Allow-Origin to the request's own Origin together with Allow-Credentials: true a vulnerability?
  7. Question 7 of 18IntermediateWhy does RFC 9700 recommend PKCE for every client, even confidential clients that already have a client_secret?
  8. Question 8 of 18IntermediateA SPA sends the ID token in the Authorization header to call an API, and the API accepts it because the signature is valid. What is the biggest problem?
  9. Question 9 of 18AdvancedThe server issues RS256 tokens and verifies with a function that takes the algorithm from the header. How does an attacker forge a token without the private key?
  10. Question 10 of 18AdvancedIn scheduled JWKS key rotation, why publish the new key to the JWKS and wait before signing with it?
  11. Question 11 of 18IntermediateThe GET /orders/:id endpoint already has an if comparing order.customerId with the current user. Why does the post still recommend moving ownership checks into the data layer (scoped repository)?
  12. Question 12 of 18AdvancedAn "import from URL" feature has the server fetch a user-supplied URL. Which SSRF defence is the most reliable?
  13. Question 13 of 18AdvancedThe code resolves the hostname, checks the IP is public, then calls fetch(url). Why is it still vulnerable to SSRF, and what is the correct fix?
  14. Question 14 of 18IntermediateA team serves user uploads from files.acme.com while the app runs on app.acme.com, believing this isolates them. What is wrong?
  15. Question 15 of 18IntermediateA dev accidentally pushed an API key to a repo. After deleting the file and force-pushing rewritten history, they consider it done. What is missing, and why is it the most important step?
  16. Question 16 of 18IntermediateWhy is using GitHub Actions OIDC to deploy to the cloud better than storing AWS_SECRET_ACCESS_KEY in GitHub secrets?
  17. Question 17 of 18AdvancedA new release of a package has valid provenance. What can you conclude?
  18. Question 18 of 18IntermediateWhy is a cooldown (rejecting versions newer than a few days) considered a good cost-to-benefit defense?

Other series

Series