| name | input-validation-skill |
| description | Validates, sanitizes, normalizes user inputs before processing. Prevents injection attacks, data corruption, runtime errors in production. Enforces schema-driven validation at entry points with explicit rejection policies for malformed payloads. |
Input Validation Skill
Enforce strict input validation before any processing to prevent security vulnerabilities and data corruption. See references/deep-reference.md for schema details.
Mindset
ALWAYS treat external input as untrusted. Validate at the boundary, not deep in business logic. Every gotcha in production traces back to skipped validation.
Core rules:
- Validate at entry points, not deep in business logic
- Reject early — fail fast on malformed input
- NEVER assume internal callers are trusted without proof
- ALWAYS return a structured error with the validation failure reason
- PREFER schema-driven validation over ad-hoc checks whenever a schema library is available
- AVOID duplicating validation logic across layers — validate once at the entry boundary
- TYPICALLY use zod, joi, or equivalent typed-schema libraries in TypeScript projects
- By default, log validation failures at WARN level unless the payload contains PII
- Consider caching compiled schemas for high-throughput paths to reduce overhead
- You may skip schema compilation caching in low-volume services where simplicity matters
When to Use
- Processing user-supplied data from forms, APIs, or CLI arguments
- Ingesting files or configuration from external sources
- Handling webhook payloads or third-party integrations
When NOT to Use
- Validating internal constants or compile-time values
- Re-validating data that has already passed a trusted validation boundary in the same request lifecycle
- skip for internal microservice calls behind a trusted network boundary verified at the infrastructure layer
NEVER bypass the validation boundary for performance reasons without an explicit security review.
Procedures
1. Define the schema
import { z } from "zod";
const InputSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150),
});
2. Parse and validate at the entry point
export const validateInput = (raw: unknown) => {
const result = InputSchema.safeParse(raw);
if (!result.success) {
throw new Error(`Validation failed: ${result.error.message}`);
}
return result.data;
};
3. Run validation before processing
bun run validate --input ./data/input.json
4. Handle errors explicitly
try {
const validated = validateInput(req.body);
await process(validated);
} catch (err) {
return res.status(400).json({ error: err.message });
}
Anti-Patterns
BAD: Skipping validation in a "trusted" internal path
function processInternal(data: any) {
db.insert(data);
}
WHY: Internal paths are often reachable from untrusted callers via indirection. NEVER assume trust without verifying the call chain.
GOOD:
function processInternal(data: unknown) {
const safe = InternalSchema.parse(data);
db.insert(safe);
}
BAD: Validating after side effects
await db.insert(data);
validate(data);
WHY: Side effects are already applied when validation fails. ALWAYS validate before any mutation.
References
| Reference | When to Load | When to Skip |
|---|
| Deep Reference: Schema Design | When defining schemas for new entry points | skip if a pre-validated schema already exists |
| Zod documentation | When composing advanced schema patterns | skip if validation is handled upstream |