Security [CRITICAL]: Unbounded password inputs enable Argon2 DoS #36

Closed
opened 2026-04-16 22:05:08 +02:00 by Hartmut · 1 comment
Owner

Problem

Multiple password fields use z.string().min(12) without .max(). Argon2 verify/hash allocates memory proportional to input — a 100 MB password triggers OOM on a public endpoint.

Evidence

  • apps/web/src/server/auth.ts:30 — LoginSchema password: z.string().min(1), no .max()
  • packages/api/src/router/auth.ts:77 — resetPassword .min(12), no .max()
  • packages/api/src/router/invite.ts:112 — acceptInvite password unbounded
  • packages/api/src/router/user-procedure-support.ts:13,18 — create/set password unbounded

Impact

Unauthenticated CPU/memory exhaustion DoS via Argon2 on huge inputs. Argon2 verify blocks the worker thread; concurrent requests saturate CPU.

Proposed Fix

Add .max(128) (OWASP ASVS 2.1.1 bound) to every password Zod schema. Argon2 treats any input >72 bytes identically after pre-hash — a .max(128) bound is safe and eliminates DoS vector.

Acceptance Criteria

  • All password fields have .max(128) bound
  • Unit test: POST with 100 KB password returns 400 (validation), not 500
  • pnpm lint + pnpm test:unit green

Parent Epic: #1
Source: Full-Codebase Security Audit 2026-04-16 (A-1, A-2, B-1)

## Problem Multiple password fields use `z.string().min(12)` without `.max()`. Argon2 verify/hash allocates memory proportional to input — a 100 MB password triggers OOM on a public endpoint. ## Evidence - `apps/web/src/server/auth.ts:30 — LoginSchema password: z.string().min(1), no .max()` - `packages/api/src/router/auth.ts:77 — resetPassword .min(12), no .max()` - `packages/api/src/router/invite.ts:112 — acceptInvite password unbounded` - `packages/api/src/router/user-procedure-support.ts:13,18 — create/set password unbounded` ## Impact Unauthenticated CPU/memory exhaustion DoS via Argon2 on huge inputs. Argon2 verify blocks the worker thread; concurrent requests saturate CPU. ## Proposed Fix Add `.max(128)` (OWASP ASVS 2.1.1 bound) to every password Zod schema. Argon2 treats any input >72 bytes identically after pre-hash — a `.max(128)` bound is safe and eliminates DoS vector. ## Acceptance Criteria - [ ] All password fields have `.max(128)` bound - [ ] Unit test: POST with 100 KB password returns 400 (validation), not 500 - [ ] `pnpm lint` + `pnpm test:unit` green --- Parent Epic: #1 Source: Full-Codebase Security Audit 2026-04-16 (A-1, A-2, B-1)
Hartmut added the security label 2026-04-16 22:05:08 +02:00
Author
Owner

Resolved in main commit 534945f (security: bound password inputs, configure pino redact, patch deps).

packages/api/src/router/user-procedure-support.ts:13,18 caps every password input at z.string().min(12).max(128). Argon2 is no longer reachable with multi-MB inputs — the Zod layer rejects before the hash call. Reset-password, set-password, and user-create paths all share the same schema.

Resolved in main commit 534945f (`security: bound password inputs, configure pino redact, patch deps`). `packages/api/src/router/user-procedure-support.ts:13,18` caps every password input at `z.string().min(12).max(128)`. Argon2 is no longer reachable with multi-MB inputs — the Zod layer rejects before the hash call. Reset-password, set-password, and user-create paths all share the same schema.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Hartmut/CapaKraken#36