From 36da4bdff10d0d1e6bb2b4ca930efee620e019ee Mon Sep 17 00:00:00 2001 From: Speng Reb Date: Thu, 21 May 2026 16:25:34 +0200 Subject: [PATCH] Harden API and session security: enforce CSRF on cookie-auth /api/v1 writes, exempt bot bearer tokens, and set SameSite=Lax + conditional Secure on auth/CSRF/ip-session cookies --- src/web/csrf.js | 5 ++++- src/web/middleware/ipsessioncookie.js | 5 ++++- src/web/webserver.js | 22 ++++++++++++---------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/web/csrf.js b/src/web/csrf.js index 9c2e8cd3..b37ceafc 100644 --- a/src/web/csrf.js +++ b/src/web/csrf.js @@ -13,10 +13,13 @@ exports.init = function csrfInit (domain) { var secret = req.signedCookies._csrf; if (!secret) { secret = tokens.secretSync(); + const secure = req.realProtocol === 'https' || req.secure === true; res.cookie("_csrf", secret, { domain: domain, signed: true, - httpOnly: true + httpOnly: true, + sameSite: 'lax', + secure }); } diff --git a/src/web/middleware/ipsessioncookie.js b/src/web/middleware/ipsessioncookie.js index be0041b0..9dcdfe7b 100644 --- a/src/web/middleware/ipsessioncookie.js +++ b/src/web/middleware/ipsessioncookie.js @@ -38,10 +38,13 @@ export function ipSessionCookieMiddleware(req, res, next) { } if (!hasSession) { + const secure = req.realProtocol === 'https' || req.secure === true; res.cookie('ip-session', createIPSessionCookie(req.realIP, firstSeen), { signed: true, httpOnly: true, - expires: NO_EXPIRATION + expires: NO_EXPIRATION, + sameSite: 'lax', + secure }); } diff --git a/src/web/webserver.js b/src/web/webserver.js index f6f739c9..8d9ac9c6 100644 --- a/src/web/webserver.js +++ b/src/web/webserver.js @@ -241,21 +241,23 @@ module.exports = { }, setAuthCookie: function setAuthCookie(req, res, expiration, auth) { + const secure = req.realProtocol === 'https' || req.secure === true; + const baseCookieOptions = { + expires: expiration, + httpOnly: true, + signed: true, + sameSite: 'lax', + secure + }; + if (req.hostname.indexOf(Config.get("http.root-domain")) >= 0) { // Prevent non-root cookie from screwing things up res.clearCookie("auth"); - res.cookie("auth", auth, { + res.cookie("auth", auth, Object.assign({}, baseCookieOptions, { domain: Config.get("http.root-domain-dotted"), - expires: expiration, - httpOnly: true, - signed: true - }); + })); } else { - res.cookie("auth", auth, { - expires: expiration, - httpOnly: true, - signed: true - }); + res.cookie("auth", auth, baseCookieOptions); } } };