fix/pwd-72byte
needs attentiond1fdb7a · fullPR #237reviewed 2026-07-24 00:05 UTC1H · 3M · 4L · 3I- Purpose
- Fix a customer-facing panic in Supabase Auth (GoTrue) where a >72-byte password caused a bcrypt panic that surfaced as an opaque 500 error.
- Goal
- Cap passwords at bcrypt's true 72-byte limit and enforce it in bytes (not character count) at every validation layer.
- Sub-goals
- SG-1: Lower PASSWORD_REQUIREMENTS.maxLength from 128 to 72 and measure in bytes via TextEncoder
- SG-2: Fix validatePasswordStrength.hasMaxLength to use byte count
- SG-3: Surface the max-length requirement visually in reset-password and invite pages
- SG-4: Add regression tests covering the 72-byte boundary and the multi-byte character case
- What
- password.schemas.ts: maxLength 128→72, add passwordByteLength() via TextEncoder, switch .max() to .refine() with byte check in both Zod schemas, fix validatePasswordStrength. Two UI pages: add PasswordRequirement indicator for hasMaxLength. New test file: 6 tests covering boundary and multi-byte cases.
- Why
- bcrypt (used by Supabase Auth/GoTrue) hard-limits passwords to 72 bytes and panics above that threshold. GoTrue's panic recovery collapses the panic into an opaque unexpected_failure 500. The old 128-char cap allowed passwords that were ≤128 chars but >72 bytes (common with accented Spanish passwords) to pass all validation and fail inside GoTrue.
- Areas
- packages/api/src/schemas+98−6apps/platform/src/app+5−0
- Blast
- 4 files, +103/−6; self-contained to password validation — no DB, no API contract shape change, no outbox
Findings · 11
correctness2
UI indicator uses tooLong ('caracteres') but hasMaxLength is byte-based
apps/platform/src/app/[locale]/invite/[token]/page.tsx:541
Duplicate of the conventions finding; flagged independently by correctness lens.
TextEncoder byte count correctly maps to Go's bcrypt UTF-8 input — confirmed sound
security2
No .max() early-exit guard before .refine() — long strings reach TextEncoder
packages/api/src/schemas/password.schemas.ts:88
Old .max(128) was removed. Add .max(500) before the refine chain for early-exit on garbage inputs.
Login page GoTrue call safe — bcrypt.CompareHashAndPassword truncates silently (no panic)
conventions2
Both UI pages show tooLong ('72 caracteres') while schema enforces bytes with tooLongBytes
apps/platform/src/app/[locale]/(auth)/reset-password/page.tsx:264
hasMaxLength is byte-based; the displayed message says 'caracteres'. A user with 40 accented chars (84 bytes) sees a green checkmark then a contradicting rejection on submit. Fix: use tooLongBytes in both pages.
tooLong message copy says '72 caracteres' for a byte limit
packages/api/src/schemas/password.schemas.ts:57
The tooLong key is misleading. Only tooLongBytes is accurate; tooLong should be deleted or renamed.
tests4
No tests for 3-byte (CJK) or 4-byte (emoji) UTF-8 characters
packages/api/src/__tests__/schemas/password.schemas.test.ts:29
Multi-byte coverage tests only 2-byte Latin accents. Add assertions for CJK (3 bytes/char) and emoji (4 bytes) inputs.
StrongPasswordSchemaES rejection not tested at 73 bytes
packages/api/src/__tests__/schemas/password.schemas.test.ts:43
Only StrongPasswordSchema is asserted to reject a 73-byte password. The ES variant has an identical refine but no rejection test.
No 71-byte boundary test
packages/api/src/__tests__/schemas/password.schemas.test.ts:35
72 (accept) and 73 (reject) are covered; 71 (one-under) is not.
makePassword helper is sound for all tested lengths (72, 73 all-ASCII)
improvement1
new TextEncoder() allocated per keystroke
packages/api/src/schemas/password.schemas.ts:49
validatePasswordStrength is called on every keystroke. A module-level const encoder = new TextEncoder() avoids repeated construction.