fix(security): set secure: true on auth cookie for production (HTTPS) #86

Open
opened 2026-03-27 09:24:33 +01:00 by marcel · 1 comment
Owner

Security Issue — MEDIUM

Found in: frontend/src/routes/login/+page.server.ts (line ~38)

The vulnerable pattern

cookies.set('auth_token', authHeader, {
    path: '/',
    httpOnly: true,
    sameSite: 'strict',
    secure: false,   // ← hardcoded false
    maxAge: 60 * 60 * 24
});

The secure flag on a cookie tells the browser to only send the cookie over HTTPS connections. With secure: false, the browser will happily send the auth_token cookie over plain HTTP — including on a public Wi-Fi network where an attacker is running a passive sniffer or a man-in-the-middle proxy.

Attack: User accesses the app over HTTP (accidentally, via a redirect, or because HTTPS isn't enforced). The auth_token cookie is transmitted in cleartext. The attacker captures it and replays it to authenticate as that user.

The fix

Derive the value from the environment rather than hardcoding false:

// frontend/src/lib/config.ts (or inline)
const isProduction = process.env.NODE_ENV === 'production';

cookies.set('auth_token', authHeader, {
    path: '/',
    httpOnly: true,
    sameSite: 'strict',
    secure: isProduction,  // true in prod, false in local dev (HTTP)
    maxAge: 60 * 60 * 24
});

Alternatively, read an explicit env var: secure: env.COOKIE_SECURE === 'true', and set COOKIE_SECURE=true in the production .env.

Also enforce HTTPS at the infrastructure level (reverse proxy / load balancer) and add an HSTS header so browsers never fall back to HTTP once they've seen the site over HTTPS:

Strict-Transport-Security: max-age=31536000; includeSubDomains

Why

httpOnly prevents JavaScript from reading the cookie (XSS protection). sameSite: strict prevents it from being sent in cross-site requests (CSRF protection). But neither helps if the cookie travels over an unencrypted connection — secure is what closes that gap.

Priority

MEDIUM — only exploitable over HTTP. As long as the app is HTTP-only on a trusted home network, risk is low. Must be fixed before any internet-facing or untrusted-network deployment.

## Security Issue — MEDIUM **Found in:** `frontend/src/routes/login/+page.server.ts` (line ~38) ### The vulnerable pattern ```typescript cookies.set('auth_token', authHeader, { path: '/', httpOnly: true, sameSite: 'strict', secure: false, // ← hardcoded false maxAge: 60 * 60 * 24 }); ``` The `secure` flag on a cookie tells the browser to **only send the cookie over HTTPS connections**. With `secure: false`, the browser will happily send the `auth_token` cookie over plain HTTP — including on a public Wi-Fi network where an attacker is running a passive sniffer or a man-in-the-middle proxy. **Attack:** User accesses the app over HTTP (accidentally, via a redirect, or because HTTPS isn't enforced). The `auth_token` cookie is transmitted in cleartext. The attacker captures it and replays it to authenticate as that user. ### The fix Derive the value from the environment rather than hardcoding `false`: ```typescript // frontend/src/lib/config.ts (or inline) const isProduction = process.env.NODE_ENV === 'production'; cookies.set('auth_token', authHeader, { path: '/', httpOnly: true, sameSite: 'strict', secure: isProduction, // true in prod, false in local dev (HTTP) maxAge: 60 * 60 * 24 }); ``` Alternatively, read an explicit env var: `secure: env.COOKIE_SECURE === 'true'`, and set `COOKIE_SECURE=true` in the production `.env`. Also enforce HTTPS at the infrastructure level (reverse proxy / load balancer) and add an `HSTS` header so browsers never fall back to HTTP once they've seen the site over HTTPS: ``` Strict-Transport-Security: max-age=31536000; includeSubDomains ``` ### Why `httpOnly` prevents JavaScript from reading the cookie (XSS protection). `sameSite: strict` prevents it from being sent in cross-site requests (CSRF protection). But neither helps if the cookie travels over an unencrypted connection — `secure` is what closes that gap. ### Priority **MEDIUM — only exploitable over HTTP. As long as the app is HTTP-only on a trusted home network, risk is low. Must be fixed before any internet-facing or untrusted-network deployment.**
marcel added the security label 2026-03-27 12:29:51 +01:00
Author
Owner

Audit note (2026-05-07) — scope is sufficient as-is, but a deeper issue is now tracked separately

This issue's scope (secure: true on the auth cookie for HTTPS) remains correct and necessary. It is not what the audit calls F-13.

The audit found the cookie value itself is Basic <base64(email:password)> — i.e., reversible plaintext credentials, not a session ID:

// frontend/src/routes/login/+page.server.ts:40-46
const credentials = btoa(`${email}:${password}`);
const authHeader = `Basic ${credentials}`;
cookies.set('auth_token', authHeader, {
    path: '/', httpOnly: true, sameSite: 'strict',
    secure: false, // ← this issue
    maxAge: 60 * 60 * 24
});

secure: true (this issue) is necessary but not sufficient — even with HTTPS, an XSS would leak the user's plaintext password (BCrypt-on-the-server is bypassed because the password travels in the cookie). That is a separate, larger refactor.

A new P0 issue will be filed separately for the redesign to an opaque session token (Spring Session JDBC is already configured but unused by the frontend). This issue stays focused on flipping the flag for the production HTTPS deploy.

Tracked in audit doc as F-13 (Critical). See docs/audits/2026-05-07-pre-prod-architectural-review.md.

## Audit note (2026-05-07) — scope is sufficient as-is, but a deeper issue is now tracked separately This issue's scope (`secure: true` on the auth cookie for HTTPS) remains correct and necessary. It is **not** what the audit calls F-13. The audit found the cookie value itself is `Basic <base64(email:password)>` — i.e., reversible plaintext credentials, not a session ID: ```typescript // frontend/src/routes/login/+page.server.ts:40-46 const credentials = btoa(`${email}:${password}`); const authHeader = `Basic ${credentials}`; cookies.set('auth_token', authHeader, { path: '/', httpOnly: true, sameSite: 'strict', secure: false, // ← this issue maxAge: 60 * 60 * 24 }); ``` `secure: true` (this issue) is necessary but not sufficient — even with HTTPS, an XSS would leak the user's plaintext password (BCrypt-on-the-server is bypassed because the password travels in the cookie). That is a separate, larger refactor. A new P0 issue will be filed separately for the redesign to an opaque session token (Spring Session JDBC is already configured but unused by the frontend). This issue stays focused on flipping the flag for the production HTTPS deploy. Tracked in audit doc as **F-13** (Critical). See `docs/audits/2026-05-07-pre-prod-architectural-review.md`.
Sign in to join this conversation.
No Label security
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#86