From 01784219f83f68c7d03eb19c35f05eeaedc74487 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 7 Aug 2026 22:58:52 -0700 Subject: sockets: verify JWT signatures on the websocket auth path (S1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The websocket auth gate used jwt.decode(), which parses the payload but never checks the signature — anyone could forge {id: } (alg:none or any key), register their socket under the victim's uid, and receive every emit for that account. Switch both track() and the "login" gate to jwt.verify(token, config.jwt), rejecting forged/altered tokens. Also project the full-record socket emits through pick(user, whitelist) so a "user" event can never carry the bcrypt hash, TOTP secret, or PIN — matching the shape /login and /me already return. Co-Authored-By: Claude Opus 4.8 --- lib/sockets.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/sockets.ts b/lib/sockets.ts index 028551ed..58673986 100644 --- a/lib/sockets.ts +++ b/lib/sockets.ts @@ -1,9 +1,21 @@ +import config from "$config"; import store from "$lib/store"; import jwt from "jsonwebtoken"; import { v4 } from "uuid"; import { err, warn } from "$lib/logging"; import { fail, getUser } from "$lib/utils"; +// Verify the token's signature — never trust jwt.decode() here. A decode-only +// gate let anyone forge {id: } (alg:none or any key) and register +// their socket under the victim's uid, receiving every emit for that account. +const verifyToken = (token) => { + try { + return jwt.verify(token, config.jwt); + } catch (e) { + return null; + } +}; + const code = 1000; const all = {}; const subscriptions = []; @@ -44,7 +56,7 @@ export const broadcast = (type, data) => { const track = async (ws, token) => { const { id } = ws; - const { id: uid } = jwt.decode(token); + const { id: uid } = verifyToken(token) || {}; if (!uid) fail("Invalid JWT token"); const user = await getUser(uid); @@ -97,7 +109,7 @@ Bun.serve({ case "login": try { - if (!data || data === "null" || !jwt.decode(data)) + if (!data || data === "null" || !verifyToken(data)) return ws.close(code, `bad token ${data}`); await track(ws, data); -- cgit v1.2.3