2.7 KiB
2.7 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Apply String Validations at Schema Definition | CRITICAL | Unvalidated strings allow SQL injection, XSS, and malformed data; validating at schema level catches issues at the boundary | 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):
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):
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:
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