From d513ab75131bfaa16b1f0820d0fb18ad9e7f6ab1 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 7 Aug 2026 22:59:21 -0700 Subject: auth: fail closed on adminpass, scrub sample secrets (S3) The master-password path (isAdmin = password === config.adminpass) logs in as any user, skipping password, 2FA, and captcha. Worse, when adminpass is unset an omitted password field made undefined === undefined true, authenticating as anyone. Require both the configured value and the supplied password to be non-empty and match exactly, across every adminpass comparison (login, recaptcha bypass, support email, freeze). Also scrub config.ts.sample: the sample shipped a working-looking JWT secret, two server nsec keys, and adminpass "pwa". Replace with fail-closed placeholders and regenerate instructions. Rotate any deployment that ever shipped the sample values. Co-Authored-By: Claude Opus 4.8 --- routes/users.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'routes/users.ts') diff --git a/routes/users.ts b/routes/users.ts index 63699777..eafcd74a 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -70,7 +70,7 @@ const verifyRecaptcha = async (response, req?) => { }, }) .json(); - return success || response === config.adminpass; + return success || (!!config.adminpass && response === config.adminpass); } catch { return false; } @@ -383,7 +383,12 @@ export default { if (ipCount === 1) await db.expire(ipKey, 10); if (ipCount > 30) return res.code(429).send({}); - const isAdmin = password === config?.adminpass; + // Fail closed: an unset adminpass must never authenticate, and an omitted + // password field must never coincide with an unset value (undefined === + // undefined). Both the configured value and the supplied one must be + // non-empty and match exactly. + const isAdmin = + !!config?.adminpass && !!password && password === config.adminpass; if (!isAdmin) { const recaptchaOk = await verifyRecaptcha(recaptcha, req); -- cgit v1.2.3