Security [MEDIUM]: Systematic Zod .max() audit — 202 unbounded z.string() sites #51

Closed
opened 2026-04-16 22:05:11 +02:00 by Hartmut · 0 comments
Owner

Problem

Grep finds 200+ z.string() without .max() across 59 router files. Arrays and records similarly unbounded in many places. Amplifies DoS surface via DB queries, JSON parsing, and regex scans. No lint rule prevents new unbounded inputs.

Evidence

  • packages/api/src/router/resource-mutations.ts:391 — batchHardDelete ids no .max()
  • packages/api/src/router/import-export-procedure-support.ts:17 — rows array unbounded
  • apps/web/src/app/api/reports/allocations/route.ts:26-27 — date params unvalidated
  • packages/api/src/router/audit-log-inputs.ts:3-14 — search/filter strings unbounded
  • packages/api/src/router/user-procedure-support.ts:11 — name no .max() (but Update caps at 200)
  • packages/api/src/router/timeline-read-schema-support.ts:6-23 — 12 filter arrays unbounded
  • packages/api/src/router/resource-skill-import.ts:7-122 — entries + portfolioUrl unbounded
  • packages/api/src/router/staffing-suggestions-read.ts:411-412 — requiredSkills unbounded
  • packages/api/src/router/notification-procedure-base.ts:217-259 — date without year bounds
  • packages/api/src/router/webhook-support.ts:10,18 — secret no min/max
  • apps/web/src/app/api/sse/timeline/route.ts:59-96 — no per-user SSE connection cap
  • apps/web/src/app/api/trpc/[trpc]/route.ts:1-89 — no request-size limit

Impact

Distributed DoS surface. Individual findings are LOW each, but sum is MEDIUM-HIGH. Any authenticated user can amplify a request by 1000× via unbounded arrays/strings.

Proposed Fix

(1) Write a coding standard in docs/zod-conventions.md: z.string() defaults to .max(500), arrays default .max(100), records need explicit schema. (2) Add ESLint rule (custom) flagging z.string(), z.array(...), z.record(...) without .max/.min. (3) Batch-PR fixing the 12 listed call-sites above. (4) Add per-user SSE connection cap (8). (5) Configure Next.js body-size limit globally.

Acceptance Criteria

  • ESLint rule in place, zero existing violations
  • Documented conventions
  • All 12 listed sites fixed
  • SSE cap enforced

Parent Epic: #1
Source: Full-Codebase Security Audit 2026-04-16 (B-4, B-5, B-6, B-10, B-13, B-16, B-17, B-18, B-19, B-20, B-21, B-22, H1)

## Problem Grep finds 200+ `z.string()` without `.max()` across 59 router files. Arrays and records similarly unbounded in many places. Amplifies DoS surface via DB queries, JSON parsing, and regex scans. No lint rule prevents new unbounded inputs. ## Evidence - `packages/api/src/router/resource-mutations.ts:391 — batchHardDelete ids no .max()` - `packages/api/src/router/import-export-procedure-support.ts:17 — rows array unbounded` - `apps/web/src/app/api/reports/allocations/route.ts:26-27 — date params unvalidated` - `packages/api/src/router/audit-log-inputs.ts:3-14 — search/filter strings unbounded` - `packages/api/src/router/user-procedure-support.ts:11 — name no .max() (but Update caps at 200)` - `packages/api/src/router/timeline-read-schema-support.ts:6-23 — 12 filter arrays unbounded` - `packages/api/src/router/resource-skill-import.ts:7-122 — entries + portfolioUrl unbounded` - `packages/api/src/router/staffing-suggestions-read.ts:411-412 — requiredSkills unbounded` - `packages/api/src/router/notification-procedure-base.ts:217-259 — date without year bounds` - `packages/api/src/router/webhook-support.ts:10,18 — secret no min/max` - `apps/web/src/app/api/sse/timeline/route.ts:59-96 — no per-user SSE connection cap` - `apps/web/src/app/api/trpc/[trpc]/route.ts:1-89 — no request-size limit` ## Impact Distributed DoS surface. Individual findings are LOW each, but sum is MEDIUM-HIGH. Any authenticated user can amplify a request by 1000× via unbounded arrays/strings. ## Proposed Fix (1) Write a coding standard in `docs/zod-conventions.md`: `z.string()` defaults to `.max(500)`, arrays default `.max(100)`, records need explicit schema. (2) Add ESLint rule (custom) flagging `z.string()`, `z.array(...)`, `z.record(...)` without `.max`/`.min`. (3) Batch-PR fixing the 12 listed call-sites above. (4) Add per-user SSE connection cap (8). (5) Configure Next.js body-size limit globally. ## Acceptance Criteria - [ ] ESLint rule in place, zero existing violations - [ ] Documented conventions - [ ] All 12 listed sites fixed - [ ] SSE cap enforced --- Parent Epic: #1 Source: Full-Codebase Security Audit 2026-04-16 (B-4, B-5, B-6, B-10, B-13, B-16, B-17, B-18, B-19, B-20, B-21, B-22, H1)
Hartmut added the security label 2026-04-16 22:05:11 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Hartmut/CapaKraken#51