← all branches

fix/pwd-72byte

needs attention
d1fdb7a · fullPR #237reviewed 2026-07-24 00:05 UTC1H · 3M · 4L · 3I
The branch
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
The changes (whole branch)
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+986apps/platform/src/app+50
Blast
4 files, +103/−6; self-contained to password validation — no DB, no API contract shape change, no outbox
accented-password-UX bcrypt-hard-limit
ci· no checks recorded — self-hosted runner not yet wiredcoderabbit· no .coderabbit.yamltests· 6 new unit tests reported passing by PR author

Findings · 11

correctness2

medium

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.

info

TextEncoder byte count correctly maps to Go's bcrypt UTF-8 input — confirmed sound

security2

low

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.

info

Login page GoTrue call safe — bcrypt.CompareHashAndPassword truncates silently (no panic)

conventions2

high

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.

medium

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

medium

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.

low

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.

low

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.

info

makePassword helper is sound for all tested lengths (72, 73 all-ASCII)

improvement1

low

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.