91 lines
2.7 KiB
Markdown
91 lines
2.7 KiB
Markdown
---
|
|
title: Apply String Validations at Schema Definition
|
|
impact: CRITICAL
|
|
impactDescription: Unvalidated strings allow SQL injection, XSS, and malformed data; validating at schema level catches issues at the boundary
|
|
tags: schema, string, validation, security
|
|
---
|
|
|
|
## Apply String Validations at Schema Definition
|
|
|
|
Plain `z.string()` accepts any string including empty strings, extremely long strings, and malicious content. Apply constraints like `min()`, `max()`, `email()`, `url()`, or `regex()` at schema definition to reject invalid data at the boundary.
|
|
|
|
**Incorrect (no string validations):**
|
|
|
|
```typescript
|
|
import { z } from 'zod'
|
|
|
|
const commentSchema = z.object({
|
|
author: z.string(), // Empty string passes
|
|
email: z.string(), // "not-an-email" passes
|
|
content: z.string(), // 10MB string passes, script tags pass
|
|
website: z.string().optional(), // "javascript:alert(1)" passes
|
|
})
|
|
|
|
// All of these pass validation
|
|
commentSchema.parse({
|
|
author: '', // Empty - who wrote this?
|
|
email: 'invalid', // Not a real email
|
|
content: '<script>alert("XSS")</script>'.repeat(100000), // XSS + huge
|
|
website: 'javascript:void(0)', // Dangerous URL
|
|
})
|
|
```
|
|
|
|
**Correct (string validations applied):**
|
|
|
|
```typescript
|
|
import { z } from 'zod'
|
|
|
|
const commentSchema = z.object({
|
|
author: z.string()
|
|
.min(1, 'Author is required')
|
|
.max(100, 'Author name too long'),
|
|
|
|
email: z.string()
|
|
.email('Invalid email address'),
|
|
|
|
content: z.string()
|
|
.min(1, 'Comment cannot be empty')
|
|
.max(5000, 'Comment too long'),
|
|
|
|
website: z.string()
|
|
.url('Invalid URL')
|
|
.refine(
|
|
url => url.startsWith('http://') || url.startsWith('https://'),
|
|
'Only http/https URLs allowed'
|
|
)
|
|
.optional(),
|
|
})
|
|
|
|
// Invalid data is rejected
|
|
commentSchema.parse({
|
|
author: '',
|
|
email: 'invalid',
|
|
content: '',
|
|
})
|
|
// ZodError with all violations listed
|
|
```
|
|
|
|
**Common string validations:**
|
|
|
|
```typescript
|
|
z.string().min(1) // Non-empty (most common need)
|
|
z.string().max(255) // Database varchar limit
|
|
z.string().length(36) // Exact length (UUIDs)
|
|
z.string().email() // Email format
|
|
z.string().url() // URL format
|
|
z.string().uuid() // UUID format
|
|
z.string().cuid() // CUID format
|
|
z.string().regex(/^[a-z0-9-]+$/) // Custom pattern (slugs)
|
|
z.string().startsWith('https://') // Prefix check
|
|
z.string().endsWith('.pdf') // Suffix check
|
|
z.string().includes('@') // Contains check
|
|
z.string().trim() // Strips whitespace (transform)
|
|
z.string().toLowerCase() // Normalizes case (transform)
|
|
```
|
|
|
|
**When NOT to use this pattern:**
|
|
- When accepting arbitrary user content for display only (sanitize on output instead)
|
|
- When building a passthrough/proxy that shouldn't validate content
|
|
|
|
Reference: [Zod API - Strings](https://zod.dev/api#strings)
|