From 6bd2515fe1f04444d228a08de62fdb3fb0fb27db Mon Sep 17 00:00:00 2001 From: aiirvizionz Date: Tue, 28 Jul 2026 10:59:04 -0600 Subject: [PATCH] fix(pwa): reject malformed password hashes --- apps/pwa/src/lib/crypto.mjs | 7 ++++++- apps/pwa/test/password-hash.test.mjs | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 apps/pwa/test/password-hash.test.mjs diff --git a/apps/pwa/src/lib/crypto.mjs b/apps/pwa/src/lib/crypto.mjs index d09a6f1..558bc6e 100644 --- a/apps/pwa/src/lib/crypto.mjs +++ b/apps/pwa/src/lib/crypto.mjs @@ -11,9 +11,14 @@ export function hashPassword(password) { export function verifyPassword(password, stored) { if (!stored || !stored.startsWith("scrypt$")) return false; - const [, saltHex, hashHex] = stored.split("$"); + const parts = stored.split("$"); + if (parts.length !== 3) return false; + const [, saltHex, hashHex] = parts; + if (!/^[0-9a-f]+$/i.test(saltHex) || !/^[0-9a-f]+$/i.test(hashHex)) return false; + if (saltHex.length % 2 !== 0 || hashHex.length % 2 !== 0) return false; const salt = Buffer.from(saltHex, "hex"); const expected = Buffer.from(hashHex, "hex"); + if (salt.length === 0 || expected.length === 0) return false; const dk = crypto.scryptSync(String(password), salt, expected.length); return dk.length === expected.length && crypto.timingSafeEqual(dk, expected); } diff --git a/apps/pwa/test/password-hash.test.mjs b/apps/pwa/test/password-hash.test.mjs new file mode 100644 index 0000000..a10f601 --- /dev/null +++ b/apps/pwa/test/password-hash.test.mjs @@ -0,0 +1,22 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { hashPassword, verifyPassword } from "../src/lib/crypto.mjs"; + +test("verifyPassword accepts a matching scrypt password hash", () => { + const stored = hashPassword("correct horse"); + + assert.equal(verifyPassword("correct horse", stored), true); + assert.equal(verifyPassword("wrong horse", stored), false); +}); + +test("verifyPassword rejects malformed scrypt hashes", () => { + for (const stored of [ + "scrypt$00$", + "scrypt$00$nothex", + "scrypt$zz$aa", + "scrypt$0$aa", + "scrypt$00$aa$extra", + ]) { + assert.equal(verifyPassword("anything", stored), false, stored); + } +});