home / skills / yanko-belov / code-craft / input-validation

input-validation skill

/skills/input-validation

This skill enforces robust input validation at boundary points, ensuring untrusted data is sanitized, typed, and validated before use.

npx playbooks add skill yanko-belov/code-craft --skill input-validation

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
4.9 KB
---
name: input-validation
description: Use when accepting user input. Use when handling request data. Use when trusting external data without validation.
---

# Input Validation

## Overview

**Never trust input. Validate everything at system boundaries.**

All external data is potentially malicious or malformed. Validate at the point of entry, fail fast on invalid input.

## When to Use

- Handling HTTP request bodies, params, headers
- Reading files or environment variables
- Accepting data from external APIs
- Any function that receives untrusted input

## The Iron Rule

```
NEVER use external input without explicit validation.
```

**No exceptions:**
- Not for "the frontend validates it"
- Not for "it's an internal API"
- Not for "we trust the source"
- Not for "it's just a simple field"

## Detection: Trust Smell

If you're using input directly, STOP:

```typescript
// ❌ VIOLATION: Trusting input
app.post('/users', async (req, res) => {
  const { email, age } = req.body;  // Direct destructure - no validation
  await db.users.create({ email, age });
  res.json({ success: true });
});
```

Problems:
- `email` could be empty, null, or not a string
- `age` could be negative, a string, or missing
- SQL injection, XSS possible depending on usage

## The Correct Pattern: Validate at Boundary

```typescript
// ✅ CORRECT: Validate with schema
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email('Invalid email format'),
  age: z.number().int().min(13).max(120),
  name: z.string().min(1).max(100),
});

app.post('/users', async (req, res) => {
  // Validate
  const result = createUserSchema.safeParse(req.body);
  
  if (!result.success) {
    return res.status(400).json({ 
      error: 'Validation failed',
      details: result.error.flatten().fieldErrors 
    });
  }
  
  // Now data is typed and validated
  const { email, age, name } = result.data;
  await db.users.create({ email, age, name });
  res.status(201).json({ success: true });
});
```

## Validation Rules

### Always Validate:
- **Type**: Is it a string, number, boolean?
- **Format**: Valid email? Valid UUID?
- **Range**: Min/max length? Numeric bounds?
- **Required**: Is it present?
- **Sanitize**: Strip dangerous characters if needed

### Validation Library Examples:

```typescript
// Zod (recommended for TypeScript)
const schema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  age: z.number().min(0).max(150),
  role: z.enum(['user', 'admin']),
  tags: z.array(z.string()).max(10),
});

// Yup
const schema = yup.object({
  email: yup.string().email().required(),
  age: yup.number().positive().integer(),
});

// class-validator
class CreateUserDto {
  @IsEmail()
  email: string;
  
  @IsInt()
  @Min(13)
  age: number;
}
```

## Pressure Resistance Protocol

### 1. "Frontend Validates It"
**Pressure:** "The form already validates this data"

**Response:** Frontend validation is for UX. Backend validation is for security. Attackers bypass frontends.

**Action:** Validate on backend regardless of frontend validation.

### 2. "It's an Internal API"
**Pressure:** "Only our services call this endpoint"

**Response:** Internal services have bugs too. Defense in depth requires validation everywhere.

**Action:** Validate internal API inputs too.

### 3. "We Trust the Source"
**Pressure:** "This data comes from our partner's API"

**Response:** Their API can have bugs, be compromised, or change format. Trust no one.

**Action:** Validate external API responses before using.

### 4. "Just a Simple Field"
**Pressure:** "It's just a string, what could go wrong?"

**Response:** Strings can be empty, too long, contain scripts, SQL, or null bytes.

**Action:** Define what "valid" means and enforce it.

## Red Flags - STOP and Reconsider

- `const { x } = req.body` without validation
- Using `any` type for input
- No error response for invalid input
- "The client sends this correctly"
- Parsing input without try/catch

**All of these mean: Add validation.**

## Quick Reference

| Input Type | Validate |
|------------|----------|
| Email | Format, length, required |
| Password | Length, complexity |
| ID | Format (uuid/int), exists |
| Number | Type, range, integer? |
| String | Length, pattern, sanitize |
| Array | Length, item validation |
| Object | Schema validation |

## Common Rationalizations (All Invalid)

| Excuse | Reality |
|--------|---------|
| "Frontend validates" | Attackers bypass frontends. |
| "Internal API" | Internal bugs exist too. |
| "We trust the source" | Sources can be compromised. |
| "Simple field" | Simple fields cause complex bugs. |
| "Validation is slow" | Validation is faster than breaches. |
| "TypeScript types are enough" | Types disappear at runtime. |

## The Bottom Line

**Validate all input. Trust nothing. Fail fast on invalid data.**

Every external boundary should have explicit validation. Use schema validation libraries. Return clear error messages for invalid input. Never let unvalidated data into your system.

Overview

This skill enforces strict input validation at system boundaries for TypeScript services. It provides guidance and patterns to validate request data, files, environment variables, and third‑party responses so only well-formed, safe data enters your system. The goal is to fail fast on invalid input and reduce security and integrity risks.

How this skill works

The skill inspects incoming data and recommends applying explicit schema validation (e.g., Zod, Yup, class-validator) at the point of entry. It shows common anti-patterns and correct patterns: parse and safeParse results, return clear 4xx errors on failure, and use validated data downstream. It emphasizes type+runtime checks, sanitization, and defense‑in‑depth for internal and external inputs.

When to use it

  • Handling HTTP request bodies, URL params, query strings, and headers
  • Reading files, environment variables, or uploaded content
  • Consuming responses from external APIs or webhooks
  • Any function or module that accepts untrusted input or user data
  • Before persisting data to DBs, calling other services, or rendering output

Best practices

  • Validate at the boundary: never destructure req.body or use input directly
  • Use runtime schema libraries (Zod recommended) so types match runtime checks
  • Return informative 4xx errors with field-level details, not internal stack traces
  • Sanitize output that will be rendered or stored; enforce length, type, and format
  • Treat internal APIs and partner data as untrusted and validate them too

Example use cases

  • POST /users endpoint: validate email, age, and name with a Zod schema before DB insert
  • API gateway: validate and normalize incoming webhook payloads before forwarding
  • Config loader: validate environment variables and fail fast on missing/invalid config
  • Microservice-to-microservice calls: validate request/response DTOs to avoid propagation of bad data
  • File uploads: validate MIME type, size limits, and parse content safely

FAQ

Is frontend validation enough?

No. Frontend validation is for UX; backend validation is required for security because clients can be bypassed or modified.

Should internal services skip validation to save time?

No. Internal services can have bugs or be compromised. Validate for defense in depth.