From 91db5a7d9ac8028e43fb0b7257a3286f2e0f4b46 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:49:19 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[MEDIUM]=20?= =?UTF-8?q?Enhance=20master=20password=20strength=20requirements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Increased master password minimum length to 12 characters in the UI. - Added a complexity requirement of at least 4 unique characters for new passwords. - Implemented a legacy minimum length check (8 characters) in the cryptographic layer as defense-in-depth while maintaining backward compatibility. - Centralized password-related limits in the `LIMITS` object in `lib/constants.js`. - Updated placeholders and error messages to reflect the new requirements. Co-authored-by: apsolut <1828768+apsolut@users.noreply.github.com> --- components/EncryptionModal.jsx | 23 ++++++++++++++++------- lib/constants.js | 3 +++ lib/crypto.js | 8 +++++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/components/EncryptionModal.jsx b/components/EncryptionModal.jsx index b5a72c0..95b33b0 100644 --- a/components/EncryptionModal.jsx +++ b/components/EncryptionModal.jsx @@ -4,7 +4,6 @@ import React, { useState } from 'react'; import { Lock, Shield, ShieldOff, Eye, EyeOff, AlertTriangle, Key } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; -import { MASTER_PASSWORD_MAX_LENGTH } from '@/lib/constants'; import { Dialog, DialogContent, @@ -27,8 +26,13 @@ export function EncryptionSetupModal({ open, onClose, onSetup }) { const handleSetup = async () => { setError(''); - if (password.length < 8) { - setError('Password must be at least 8 characters'); + if (password.length < LIMITS.MASTER_PASSWORD_MIN) { + setError(`Password must be at least ${LIMITS.MASTER_PASSWORD_MIN} characters`); + return; + } + + if (new Set(password).size < LIMITS.MASTER_PASSWORD_MIN_UNIQUE) { + setError(`Password must contain at least ${LIMITS.MASTER_PASSWORD_MIN_UNIQUE} unique characters`); return; } @@ -75,7 +79,7 @@ export function EncryptionSetupModal({ open, onClose, onSetup }) {
setPassword(e.target.value)} maxLength={LIMITS.MASTER_PASSWORD} @@ -355,8 +359,13 @@ export function ChangePasswordModal({ open, onClose, onChange }) { const handleChange = async () => { setError(''); - if (newPassword.length < 8) { - setError('New password must be at least 8 characters'); + if (newPassword.length < LIMITS.MASTER_PASSWORD_MIN) { + setError(`New password must be at least ${LIMITS.MASTER_PASSWORD_MIN} characters`); + return; + } + + if (new Set(newPassword).size < LIMITS.MASTER_PASSWORD_MIN_UNIQUE) { + setError(`New password must contain at least ${LIMITS.MASTER_PASSWORD_MIN_UNIQUE} unique characters`); return; } @@ -419,7 +428,7 @@ export function ChangePasswordModal({ open, onClose, onChange }) { setNewPassword(e.target.value)} maxLength={LIMITS.MASTER_PASSWORD} diff --git a/lib/constants.js b/lib/constants.js index 1cd3a86..1a5fbf6 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -10,6 +10,9 @@ export const LIMITS = { FIELD_LABEL: 50, FIELD_VALUE: 10000, MASTER_PASSWORD: 128, + MASTER_PASSWORD_MIN: 12, + MASTER_PASSWORD_LEGACY_MIN: 8, + MASTER_PASSWORD_MIN_UNIQUE: 4, MAX_PROJECTS: 50, MAX_SNIPPETS_PER_PROJECT: 200, MAX_FIELDS_PER_SNIPPET: 20, diff --git a/lib/crypto.js b/lib/crypto.js index 2994db3..0f352eb 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -1,6 +1,6 @@ import nacl from 'tweetnacl'; import naclUtil from 'tweetnacl-util'; -import { SECURITY } from './constants'; +import { SECURITY, LIMITS } from './constants'; const { encodeBase64, decodeBase64, encodeUTF8, decodeUTF8 } = naclUtil; // Constants @@ -57,6 +57,9 @@ export function generateNonce() { * Returns base64 encoded: salt + nonce + ciphertext */ export async function encrypt(plaintext, password) { + if (password.length < LIMITS.MASTER_PASSWORD_LEGACY_MIN) { + throw new Error(`Password must be at least ${LIMITS.MASTER_PASSWORD_LEGACY_MIN} characters`); + } const salt = generateSalt(); const key = await deriveKey(password, salt); const nonce = generateNonce(); @@ -114,6 +117,9 @@ export async function decrypt(encryptedData, password) { * This is stored to verify the password is correct without storing it */ export async function createPasswordHash(password) { + if (password.length < LIMITS.MASTER_PASSWORD_LEGACY_MIN) { + throw new Error(`Password must be at least ${LIMITS.MASTER_PASSWORD_LEGACY_MIN} characters`); + } const salt = generateSalt(); const key = await deriveKey(password, salt);