Claude
Skills
Sign in
Back

secure-error-handling

Included with Lifetime
$97 forever

Implement secure error handling to prevent information leakage and provide appropriate error responses. Use this skill when you need to handle errors in API routes, prevent stack trace exposure, implement environment-aware error messages, or use the error handler utilities. Triggers include "error handling", "handle errors", "error messages", "information leakage", "stack trace", "handleApiError", "production errors", "error responses".

Backend & APIs

What this skill does


# Secure Error Handling - Preventing Information Leakage

## The Error Message Problem

Error messages are designed to help developers debug. But in production, **detailed errors help attackers more than they help users**.

### What Attackers Learn from Error Messages

**Database structure:**
```
Error: column 'credit_cards.number' does not exist
```
→ Attacker now knows you have a `credit_cards` table

**File paths:**
```
Error at /var/www/app/lib/payment.js:47
```
→ Attacker learns your directory structure

**Dependencies:**
```
Stripe API error: Invalid API key format
```
→ Attacker knows you use Stripe

**System info:**
```
PostgreSQL 9.4 connection failed
```
→ Attacker learns your database version and can look up known vulnerabilities

### Real-World Information Leakage

According to SANS Institute research, **74% of successful attacks start with reconnaissance** phase where attackers gather information about the target system. **Error messages are a primary source** of this intelligence.

**Equifax Breach (2017):**
Detailed error messages revealed they were using Apache Struts with a known vulnerability. Attackers exploited this revealed information.

## Our Error Handling Architecture

### Environment-Aware Error Responses

**Development Mode:**
```javascript
{
  error: "Database connection failed",
  stack: "Error: connection timeout at db.connect (database.js:42:15)...",
  context: "user-profile-update",
  timestamp: "2025-10-15T10:30:00Z"
}
```
→ Developers get full details for debugging

**Production Mode:**
```javascript
{
  error: "Internal server error",
  message: "An unexpected error occurred. Please try again later."
}
```
→ Users get safe, generic message

### The Logging Strategy

**All errors are logged server-side** with full details (for investigation), but **only generic messages are sent to clients** in production. This gives us debugging capability without information leakage.

## Implementation Files

- `lib/errorHandler.ts` - 5 error handlers for different scenarios

## Available Error Handlers

### 1. handleApiError(error, context)

**Use for:** Unexpected errors (HTTP 500)

```typescript
import { handleApiError } from '@/lib/errorHandler';

async function handler(request: NextRequest) {
  try {
    // Risky operation
    await processPayment(data);
    return NextResponse.json({ success: true });

  } catch (error) {
    return handleApiError(error, 'payment-processing');
    // Production: "Internal server error"
    // Development: Full stack trace
  }
}
```

**Returns:**
- **Development:** Full error with stack trace
- **Production:** Generic "Internal server error" message
- **HTTP Status:** 500

### 2. handleValidationError(message, details)

**Use for:** Input validation failures (HTTP 400)

```typescript
import { handleValidationError } from '@/lib/errorHandler';

if (!isValidEmail(email)) {
  return handleValidationError(
    'Validation failed',
    { email: 'Invalid email format' }
  );
}
```

**Returns:**
```json
{
  "error": "Validation failed",
  "details": {
    "email": "Invalid email format"
  }
}
```
- **HTTP Status:** 400
- **Both dev and production:** Returns detailed field errors (helps users fix input)

### 3. handleForbiddenError(message)

**Use for:** Authorization failures (HTTP 403)

```typescript
import { handleForbiddenError } from '@/lib/errorHandler';

// Check if user owns this resource
if (resource.userId !== userId) {
  return handleForbiddenError('You do not have access to this resource');
}
```

**Returns:**
```json
{
  "error": "Forbidden",
  "message": "You do not have access to this resource"
}
```
- **HTTP Status:** 403
- **Both dev and production:** Returns the provided message

### 4. handleUnauthorizedError(message)

**Use for:** Authentication failures (HTTP 401)

```typescript
import { handleUnauthorizedError } from '@/lib/errorHandler';
import { auth } from '@clerk/nextjs/server';

const { userId } = await auth();
if (!userId) {
  return handleUnauthorizedError('Authentication required');
}
```

**Returns:**
```json
{
  "error": "Unauthorized",
  "message": "Authentication required"
}
```
- **HTTP Status:** 401
- **Both dev and production:** Returns the provided message
- **Default message:** "Authentication required" if no message provided

### 5. handleNotFoundError(resource)

**Use for:** Resource not found (HTTP 404)

```typescript
import { handleNotFoundError } from '@/lib/errorHandler';

const post = await db.posts.findOne({ id: postId });
if (!post) {
  return handleNotFoundError('Post');
}
```

**Returns:**
```json
{
  "error": "Not found",
  "message": "Post not found"
}
```
- **HTTP Status:** 404
- **Both dev and production:** Returns resource-specific message

## Complete Error Handling Examples

### Example 1: Protected API Route with Full Error Handling

```typescript
// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@clerk/nextjs/server';
import { validateRequest } from '@/lib/validateRequest';
import { idSchema } from '@/lib/validation';
import {
  handleApiError,
  handleUnauthorizedError,
  handleForbiddenError,
  handleNotFoundError,
  handleValidationError
} from '@/lib/errorHandler';

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    // Authentication check
    const { userId } = await auth();
    if (!userId) {
      return handleUnauthorizedError('Please sign in to view posts');
    }

    // Validate ID parameter
    const validation = validateRequest(idSchema, params.id);
    if (!validation.success) {
      return handleValidationError('Invalid post ID', { id: 'Must be valid ID' });
    }

    const postId = validation.data;

    // Fetch post
    const post = await db.posts.findOne({ id: postId });

    // Handle not found
    if (!post) {
      return handleNotFoundError('Post');
    }

    // Check authorization
    if (post.userId !== userId && !post.isPublic) {
      return handleForbiddenError('You do not have access to this post');
    }

    return NextResponse.json({ post });

  } catch (error) {
    // Catch unexpected errors
    return handleApiError(error, 'get-post');
  }
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const { userId } = await auth();
    if (!userId) {
      return handleUnauthorizedError();
    }

    const validation = validateRequest(idSchema, params.id);
    if (!validation.success) {
      return handleValidationError('Invalid post ID', validation.error);
    }

    const postId = validation.data;
    const post = await db.posts.findOne({ id: postId });

    if (!post) {
      return handleNotFoundError('Post');
    }

    // Only post owner can delete
    if (post.userId !== userId) {
      return handleForbiddenError('Only the post author can delete this post');
    }

    await db.posts.delete({ id: postId });

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

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

### Example 2: Payment Processing with Detailed Error Handling

```typescript
// app/api/process-payment/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { withCsrf } from '@/lib/withCsrf';
import { auth } from '@clerk/nextjs/server';
import { handleApiError, handleUnauthorizedError, handleValidationError } from '@/lib/errorHandler';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

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

    const body = await request.json();
    const { amount, paymentMethodId } = body;

    // Validate amount
    if (!amount || amount < 50) {
      return handleValidationError('Invalid amount', {
        amount: 'Amount must be at least $0.

Related in Backend & APIs