Client-side password validation is trivially bypassed by calling the API directly with curl or any HTTP client. CWE-521 (Weak Password Requirements) is only exploitable when server-side enforcement is missing. NIST 800-63B §5.1.1.2 (Memorized Secret Verifiers) sets a minimum of 8 characters and specifically warns against composition rules; PCI-DSS Req-8.3.6 requires at least 12 characters. OWASP A07 lists weak password policies as an authentication failure. A single-character password accepted by your API means every user who sets a simple password has a credential that can be brute-forced in seconds from any leaked hash.
Medium because weak password policy alone does not cause a breach, but it makes brute-force and credential stuffing attacks dramatically more effective when combined with any other failure.
Enforce password requirements in the API route, not just the React component. Run validation before hashing so invalid passwords never touch the database:
function validatePassword(password: string): string | null {
if (password.length < 12) return 'Password must be at least 12 characters'
if (!/[A-Z]/.test(password)) return 'Must include an uppercase letter'
if (!/[0-9]/.test(password)) return 'Must include a number'
return null
}
export async function POST(request: Request) {
const { email, password } = await request.json()
const err = validatePassword(password)
if (err) return Response.json({ error: err }, { status: 400 })
// proceed to hash and create user
}
For Supabase Auth, configure minimum password length in the Supabase Dashboard under Auth > Settings > Password Requirements — Supabase enforces this server-side. Consider adding zxcvbn for entropy-based strength estimation alongside rule-based checks.
ID: saas-authentication.password-credential.password-strength-enforced
Severity: medium
What to look for: Find the registration and password change handlers. Check if password validation is applied server-side (not just client-side) before hashing. Look for minimum length enforcement (ideally 12+ characters), and any complexity rules. Also check if the password is checked against common password lists (e.g., zxcvbn library integration). Count every password validation rule enforced by the application (minimum length, character requirements, breach database check, etc.) and enumerate each.
Pass criteria: Password length minimum is enforced server-side (at least 8 characters, preferably 12). Validation runs before hashing and returns a clear error if requirements aren't met. Client-side validation may exist as UX, but server-side is the authoritative check. For Supabase Auth: password strength policies configured in the Supabase Dashboard (Auth > Settings > Password Requirements) are enforced server-side by Supabase and satisfy this check. If the project uses Supabase Auth and has no custom password validation code, mark as PASS with detail noting that enforcement is via Supabase dashboard configuration (not verifiable from code alone — this is an accepted limitation). Report even on pass: "X password validation rules enforced. Minimum length: Y characters. Breach database check: Z."
Fail criteria: No server-side password strength validation — any password including single-character passwords is accepted. Or validation exists only on the client (in browser JS) which can be trivially bypassed by calling the API directly.
Skip (N/A) when: No local password authentication. Signal: OAuth-only or managed provider.
Cross-reference: The password-hashing-modern check verifies the storage mechanism that makes a strong password policy worthwhile.
Detail on fail: "Registration endpoint at /api/auth/register does not validate password length or strength — accepts single-character passwords" or "Password strength check exists only in the React component, not in the API route — API accepts any password".
Remediation: Server-side password validation is non-negotiable — anyone can call your API directly, bypassing your frontend:
function validatePassword(password: string): string | null {
if (password.length < 12) return 'Password must be at least 12 characters'
if (!/[A-Z]/.test(password)) return 'Password must include an uppercase letter'
if (!/[0-9]/.test(password)) return 'Password must include a number'
return null // null = valid
}
export async function POST(request: Request) {
const { email, password } = await request.json()
const passwordError = validatePassword(password)
if (passwordError) return Response.json({ error: passwordError }, { status: 400 })
// ... proceed with registration
}
Consider integrating the zxcvbn library for smarter strength estimation beyond simple rules.