agentsclimarketplace

Input validation guide

Skill VersoXBT/claude-initial-setup/skills/security/input-validation-guide

Validate and sanitize all user input using schema validation libraries. Activate whenever the user writes form handlers, API endpoints, request parsers, data processing functions, or any code that accepts external input. Also activate when the user asks about validation libraries like Zod, Joi, or Pydantic, or discusses input sanitization.From its SKILL.md

Install
npx -y skills add VersoXBT/claude-initial-setup --skill input-validation-guide

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.7 KB, ~1.8k tokens by cl100k_base, as published. Nobody here has run it

Input Validation Guide

Validate all external input at system boundaries using schema validation libraries. Reject invalid data early with clear error messages. Never trust client-side validation as the sole defense.

When to Use

  • Writing API endpoint handlers that accept request bodies, query params, or path params
  • Building form validation logic (server-side)
  • Processing data from external APIs or file uploads
  • Parsing configuration files or environment variables
  • Any function that receives data from outside the trust boundary

Core Patterns

Zod (TypeScript)

The standard for TypeScript schema validation. Parse, don't validate.

import { z } from 'zod';

// Define schemas as the source of truth
const CreateUserSchema = z.object({
  email: z.string().email('Invalid email format'),
  name: z.string().min(1, 'Name required').max(100),
  age: z.number().int().min(13, 'Must be 13 or older').max(150),
  role: z.enum(['user', 'admin', 'moderator']).default('user'),
  website: z.string().url().optional(),
});

// Infer TypeScript types from schemas
type CreateUserInput = z.infer<typeof CreateUserSchema>;

// Parse at the boundary, use typed data internally
app.post('/api/users', async (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({
      error: 'Validation failed',
      issues: result.error.issues.map(i => ({
        field: i.path.join('.'),
        message: i.message,
      })),
    });
  }
  // result.data is fully typed as CreateUserInput
  const user = await createUser(result.data);
  res.json(user);
});

Advanced Zod patterns for complex validation:

// Transform and refine
const SearchParamsSchema = z.object({
  page: z.coerce.number().int().min(1).default(1),
  limit: z.coerce.number().int().min(1).max(100).default(20),
  sort: z.enum(['name', 'date', 'relevance']).default('relevance'),
  q: z.string().trim().min(1).max(200).optional(),
});

// Discriminated unions for type-safe variants
const PaymentSchema = z.discriminatedUnion('method', [
  z.object({
    method: z.literal('card'),
    cardNumber: z.string().regex(/^\d{16}$/),
    expiry: z.string().regex(/^\d{2}\/\d{2}$/),
    cvv: z.string().regex(/^\d{3,4}$/),
  }),
  z.object({
    method: z.literal('bank_transfer'),
    accountNumber: z.string().min(8).max(20),
    routingNumber: z.string().length(9),
  }),
]);

// Custom refinements with cross-field validation
const DateRangeSchema = z.object({
  startDate: z.coerce.date(),
  endDate: z.coerce.date(),
}).refine(
  data => data.endDate > data.startDate,
  { message: 'End date must be after start date', path: ['endDate'] }
);

Pydantic (Python)

The standard for Python data validation using type annotations.

from pydantic import BaseModel, Field, field_validator, EmailStr
from datetime import date
from enum import Enum

class UserRole(str, Enum):
    USER = "user"
    ADMIN = "admin"
    MODERATOR = "moderator"

class CreateUserRequest(BaseModel):
    email: EmailStr
    name: str = Field(min_length=1, max_length=100)
    age: int = Field(ge=13, le=150)
    role: UserRole = UserRole.USER
    website: str | None = None

    @field_validator("name")
    @classmethod
    def name_must_not_be_empty(cls, v: str) -> str:
        stripped = v.strip()
        if not stripped:
            raise ValueError("Name cannot be blank")
        return stripped

# FastAPI integration - validation is automatic
@app.post("/api/users")
async def create_user(data: CreateUserRequest):
    # data is already validated and typed
    return await save_user(data)

Allowlists vs Denylists

Always prefer allowlists. Denylists are inherently incomplete.

// WRONG: Denylist approach - always has gaps
function sanitizeFilename(name: string): string {
  return name.replace(/[<>:"/\\|?*]/g, ''); // What about null bytes? Unicode tricks?
}

// CORRECT: Allowlist approach - only permit known-good characters
function sanitizeFilename(name: string): string {
  const sanitized = name.replace(/[^a-zA-Z0-9._-]/g, '');
  if (!sanitized || sanitized.startsWith('.')) {
    throw new Error('Invalid filename');
  }
  return sanitized;
}

// CORRECT: Allowlist for content types
const ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'application/pdf']);
function validateContentType(type: string): boolean {
  return ALLOWED_TYPES.has(type);
}

Type Coercion Attack Prevention

Prevent attacks that exploit JavaScript's loose type coercion.

// WRONG: Vulnerable to type coercion
app.get('/api/users', (req, res) => {
  // req.query.admin could be "true" (string), true (if parsed), or ["true"] (array)
  if (req.query.admin == true) { // Loose equality - dangerous
    return getAdminData();
  }
});

// CORRECT: Strict parsing with Zod
const QuerySchema = z.object({
  admin: z.enum(['true', 'false']).transform(v => v === 'true').optional(),
  id: z.coerce.number().int().positive(), // Explicit coercion
});

app.get('/api/users', (req, res) => {
  const query = QuerySchema.parse(req.query);
  // query.admin is boolean | undefined, query.id is number
});

// WRONG: JSON.parse without validation
const config = JSON.parse(userInput); // Could be any type

// CORRECT: Parse then validate
const rawData = JSON.parse(userInput);
const config = ConfigSchema.parse(rawData);

Sanitization for Specific Contexts

Apply context-appropriate sanitization after validation.

import DOMPurify from 'dompurify';
import sqlstring from 'sqlstring';

// HTML content: strip dangerous tags
function sanitizeHtml(input: string): string {
  return DOMPurify.sanitize(input, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'li'],
    ALLOWED_ATTR: [],
  });
}

// Path traversal prevention
function safePath(basedir: string, userPath: string): string {
  const resolved = path.resolve(basedir, userPath);
  if (!resolved.startsWith(basedir)) {
    throw new Error('Path traversal detected');
  }
  return resolved;
}

Anti-Patterns

  • Relying solely on client-side validation (easily bypassed)
  • Using denylists instead of allowlists for filtering
  • Validating deep inside business logic instead of at the boundary
  • Using loose equality (==) instead of strict equality (===) in JavaScript
  • Trusting Content-Type headers without verifying actual content
  • Silently coercing invalid data instead of rejecting it
  • Writing custom regex for email, URL, or date validation instead of using library validators
  • Catching validation errors and returning generic "Bad Request" without field-level detail

Quick Reference

LanguageLibraryKey Pattern
TypeScriptZodschema.safeParse(input) returns { success, data, error }
TypeScriptJoischema.validate(input) returns { value, error }
PythonPydanticClass-based models with type annotations
GovalidatorStruct tags: validate:"required,email"
PrincipleRule
Validate earlyAt system boundaries, not deep in business logic
AllowlistPermit known-good, reject everything else
Parse, don't validateTransform raw input into typed domain objects
Fail loudlyReturn specific field-level error messages
Never trust clientServer-side validation is mandatory

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most quality gates skills give in ~1.8k tokens

Counted across 1,524 of the 2,830 authors here whose files we hold, read 2026-09-06

  • Read full output and check exit codein 45 of 1524, across 40 files
  • Verify output confirms the claimin 44 of 1524, across 39 files
  • Identify the command that proves the claimin 43 of 1524, across 39 files
  • Execute the full verification commandin 36 of 1524, across 30 files
  • Produce a verification reportin 34 of 1524, across 18 files
  • Review git diff changesin 30 of 1524, across 16 files
  • Fix build failures immediatelyin 29 of 1524, across 9 files
  • Group findings by severityin 28 of 1524
  • State claim only with evidencein 27 of 1524, across 22 files
  • Verify regression tests with red-green cyclein 26 of 1524, across 22 files
  • Run the full test suitein 26 of 1524, across 25 files
  • Run test suite with coveragein 25 of 1524, across 10 files

Said here and by no other author read

  • Validate all external input at system boundaries
  • Use schema validation libraries for all input
  • Reject invalid data early with clear error messages
  • Prefer allowlists over denylists for filtering
  • Parse raw input into typed domain objects
  • Apply context-appropriate sanitization after validation

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.