CSRF (cross-site request forgery)
Another site quietly makes your browser fire a real logged-in request at your app, and your app cannot tell the user did not mean it.
See it
What it is
Cookies are ambient: once a user is logged in, the browser attaches the session cookie to requests for your site no matter who started them. So an attacker's page can host a hidden form that auto-submits a POST to yourbank.com/transfer, and your server sees a perfectly authenticated request from a real user. The attacker cannot read the response, which is why CSRF is about actions, not theft: transfer money, change email, delete account, add an admin.
Three defenses, best stacked. SameSite cookies (Lax is the modern browser default, Strict where you can afford it) stop the cookie riding along on cross-site POSTs. Anti-CSRF tokens: a random value tied to the session, rendered into the form and checked on submit, which the attacker's page cannot read or guess. Origin checks: reject state-changing requests whose Origin or Sec-Fetch-Site header is not yours. Most frameworks ship one of these; the bug is usually a route that opted out.
Gotcha: CSRF only exists where the browser authenticates you automatically, so cookie sessions and HTTP basic auth are exposed while an Authorization: Bearer header your JavaScript sets is not. Second gotcha: SameSite=Lax still permits top-level GET navigation, so any GET endpoint that changes state (a /logout or /delete?id=3 link) is wide open. Keep mutations on POST, PUT, PATCH, DELETE. Also note that an XSS hole defeats every CSRF defense at once, since script on your own origin can just read the token.
Ask AI for it
Harden this app against CSRF. Set session cookies to SameSite=Lax (or Strict where no cross-site entry is needed) with Secure and HttpOnly. Add the framework's CSRF token middleware to every state-changing route, render the token into each form and into the fetch headers for AJAX calls, and reject requests whose token is missing or stale. On top of that, verify the Origin or Sec-Fetch-Site header on POST, PUT, PATCH and DELETE and return 403 when it is cross-site. Move any state-changing GET endpoint (logout, delete, toggle links) to POST. List any route deliberately exempted, such as webhook receivers, and say why.