Weak password requirements are the primary enabler of credential-stuffing attacks: attackers replay breached credential lists at scale and succeed wherever short or trivially guessable passwords are allowed. NIST 800-63B and NIST 800-53 rev5 IA-5(1) mandate a minimum of 8 characters (NIST guidance recommends 12+) with complexity requirements, and AC-7 requires rate-limiting after repeated failed authentication attempts. CWE-521 (Weak Password Requirements) and OWASP A07 both rank this as a systemic authentication failure. Client-only validation is bypassed by any direct API call, so server-side enforcement is non-negotiable.
High because weak or unrate-limited password endpoints are the direct entry point for automated credential-stuffing and brute-force campaigns targeting every user account.
Enforce password complexity and rate-limiting server-side in every password-accepting API route. Client-side validation can improve UX but must never be the sole gate.
// lib/auth-validation.ts
import { z } from 'zod'
export const passwordSchema = z.string()
.min(12, 'Minimum 12 characters')
.regex(/[A-Z]/, 'At least one uppercase letter')
.regex(/[a-z]/, 'At least one lowercase letter')
.regex(/[0-9]/, 'At least one digit')
.regex(/[^A-Za-z0-9]/, 'At least one special character')
Apply rate-limiting at the route level using @upstash/ratelimit or express-rate-limit: no more than 5 password submissions per minute per IP and per user identifier. Validate with Zod before any database interaction — never trust client-supplied values.
ID: gov-fisma-fedramp.access-control.password-requirements
Severity: high
What to look for: Enumerate all password-related endpoints: registration, password reset, and password change flows. For each, quote the validation schema or regex pattern used. Check for minimum length requirement (at least 12 characters per NIST 800-63B), complexity requirements (at least 3 of 4 character types: uppercase, lowercase, numbers, symbols), and rate-limiting on password submission endpoints. Count the rate-limit threshold configured.
Pass criteria: Password policy enforces minimum 12 characters, at least 3 of 4 character types (uppercase, lowercase, numbers, symbols), and rate-limiting is present on password submission endpoints with no more than 5 attempts per minute per user/IP. Validation runs server-side (not client-only). Report the ratio of endpoints with validation to total password endpoints.
Fail criteria: Passwords allow fewer than 8 characters, no complexity requirements, or no rate-limiting on password endpoints. Do not pass when validation exists only on the client-side with no server-side enforcement.
Skip (N/A) when: The project has no user authentication (no login/registration).
Detail on fail: Specify which requirements are missing. Example: "Password minimum length is only 8 chars (need 12+). No complexity requirement enforced. Rate-limiting not found on POST /api/auth/register endpoint."
Remediation: Implement strong password requirements in your authentication API. Use libraries like zod or joi for validation:
// lib/auth-validation.ts
import { z } from 'zod'
const passwordSchema = z.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Must include uppercase letter')
.regex(/[a-z]/, 'Must include lowercase letter')
.regex(/[0-9]/, 'Must include number')
.regex(/[!@#$%^&*]/, 'Must include special character')
// app/api/auth/register/route.ts
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 5,
message: 'Too many password attempts'
})
export const POST = limiter(async (req) => {
const { password } = await req.json()
const result = passwordSchema.safeParse(password)
if (!result.success) {
return Response.json({ error: 'Invalid password' }, { status: 400 })
}
// Continue with registration...
})