Crux Read real cookie configs and route handlers, predict the CSRF behaviour, and pick the highest-leverage fix a senior reviewer would make first.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
CSRF bugs live in cookie attributes and route handlers, not in prose. Read each snippet, predict whether a forged request goes through, and choose the fix a senior reviewer would flag first.
Goal
Practise the loop you run in every security review: read the cookie config and the handler, predict where a forged write lands, and reach for the highest-leverage fix before reasoning about exotic bypasses.
Snippet 1 — the cookie config
// Express session cookieapp.use(session({ secret: process.env.SECRET, cookie: { httpOnly: true, secure: true, sameSite: 'none', // <-- needed so the embedded widget on partner.com works },}));
Quiz
Completed
The team set sameSite: 'none' so the session works inside an iframe on partner.com. What CSRF posture does this create, and what must follow?
Heads-up httpOnly stops JavaScript from reading the cookie (an XSS concern); it does nothing to stop the browser from auto-sending it cross-site. With SameSite=None there is no SameSite protection left to lean on.
Heads-up secure is required syntactically with SameSite=None, but it only forces HTTPS transport. The forged cross-site request is HTTPS too; SameSite=None still ships the cookie. Encryption is orthogonal to CSRF.
Heads-up An embedded context can issue requests that carry a SameSite=None cookie cross-site, and the whole point of None is to send the cookie everywhere. State changes are fully reachable; a token defense is required.
This plain double-submit check passes when the cookie value equals the header value. What's the weakness a senior reviewer flags, and the fix?
Heads-up String comparison is not the defect. The structural weakness is that the pattern assumes the attacker can't write your cookie — break that assumption (subdomain XSS, injection) and matching values are trivial to forge.
Heads-up The header can't be READ or set cross-site by script, true — but the attacker doesn't need to read it; they need to control both halves, which a cookie-write primitive grants. Plain double-submit is weaker than it looks.
Heads-up Stateless can be secure: the HMAC-signed double-submit binds the token to the session secret so a planted cookie value won't validate, with no server storage. The fix is binding, not abandoning statelessness.
Snippet 3 — the route handler
// "Unsubscribe with one click from the email link"app.get('/account/email-prefs/unsubscribe', requireSession, (req, res) => { db.users.update(req.session.userId, { subscribed: false }); // mutates! res.send('You are unsubscribed.');});
Quiz
Completed
Sessions here are SameSite=Lax and there's a CSRF token on all POST routes. Is this GET handler safe? Why or why not?
Heads-up requireSession is satisfied by the victim's own auto-attached Lax cookie on a forged top-level GET. Authentication is present; intent is not. The mutating GET is the hole.
Heads-up Lax deliberately sends the cookie on top-level GET navigations. That's precisely the case this handler is exposed to; Lax does not block it.
Heads-up Idempotency is irrelevant to CSRF: one forged request already flips subscribed to false against the user's will. The defect is mutating on GET, where the token check doesn't run.
Snippet 4 — the Origin check
function checkOrigin(req, res, next) { const origin = req.get('Origin'); if (origin === 'https://app.example.com') return next(); return res.status(403).send('Bad origin');}
Quiz
Completed
Used as the ONLY CSRF defense on state-changing routes, what breaks, and how should this layer be used?
Heads-up Origin is browser-set and not script-forgeable, true, but it isn't always present, and a sole negative filter proves no intent. It belongs alongside a token, with a Referer fallback, not on its own.
Heads-up Origin is set by the browser and can't be spoofed by page script in a normal cross-site request — that's its strength. The real issues are absence/strictness and lack of positive proof, so it's a layer, not the whole defense.
Heads-up Origin is the more reliable signal (sent on cross-site state-changing requests and minimal); Referer is the fallback and is more often stripped by privacy settings or referrer-policy. The order is Origin first, Referer as fallback.
Recap
Every CSRF review is read in cookie attributes and handlers: SameSite=None opts you back into the full pre-2020 surface and mandates a token defense; plain double-submit trusts that only you can write your cookie, so the HMAC-signed variant is the fix; a mutation on a GET is forgeable through a top-level Lax navigation and must move to POST behind a token; and an Origin check is a cheap defense-in-depth filter — falling back to Referer — never the sole lock. Spot the structural defect, fix it at the highest-leverage layer, then confirm the forged request can no longer land.