Files
gh-djankies-claude-configs-…/skills/preventing-error-exposure/SKILL.md
2025-11-29 18:22:25 +08:00

12 KiB

name, description, allowed-tools, version
name description allowed-tools version
preventing-error-exposure Prevent leaking database errors and P-codes to clients. Use when implementing API error handling or user-facing error messages. Read, Write, Edit 1.0.0

Security: Error Exposure Prevention

This skill teaches Claude how to handle Prisma errors securely by transforming detailed database errors into user-friendly messages while preserving debugging information in logs.


This skill prevents leaking sensitive database information (P-codes, table names, column details, constraints) to API clients while maintaining comprehensive server-side logging for debugging. This skill activates when: - Implementing API error handlers or middleware - Working with try/catch blocks around Prisma operations - Building user-facing error responses - Setting up logging infrastructure - User mentions error handling, error messages, or API responses Prisma errors contain detailed database information including: - P-codes (P2002, P2025, etc.) revealing database operations - Table and column names exposing schema structure - Constraint names showing relationships - Query details revealing business logic

Security Risk: Exposing this information helps attackers:

  • Map database schema
  • Identify validation rules
  • Craft targeted attacks
  • Discover business logic

Solution Pattern: Transform errors for clients, log full details server-side.

Key capabilities:

  1. P-code to user message transformation
  2. Error sanitization removing sensitive details
  3. Server-side logging with full context
  4. Production-ready error middleware
## Standard Workflow

Phase 1: Error Detection

  1. Wrap Prisma operations in try/catch
  2. Identify error type (Prisma vs generic)
  3. Extract P-code if present

Phase 2: Error Transformation

  1. Map P-code to user-friendly message
  2. Remove database-specific details
  3. Generate safe generic message for unknown errors
  4. Preserve error context for logging

Phase 3: Response and Logging

  1. Log full error details server-side (P-code, stack, query)
  2. Return sanitized message to client
  3. Include generic error ID for support correlation
## Production vs Development

Development Environment:

  • Log full error details including stack traces
  • Optionally include P-codes in API response for debugging
  • Show detailed validation errors
  • Enable query logging

Production Environment:

  • NEVER expose P-codes to clients
  • Log errors with correlation IDs
  • Return generic user messages
  • Monitor error rates for P2024 (connection timeout)
  • Alert on P2002 spikes (potential brute force)

Framework-Specific Patterns

Next.js App Router:

export async function POST(request: Request) {
  try {
    const data = await request.json()
    const result = await prisma.user.create({ data })
    return Response.json(result)
  } catch (error) {
    return handlePrismaError(error)
  }
}

Express/Fastify:

app.use((err, req, res, next) => {
  if (isPrismaError(err)) {
    const { status, message, errorId } = transformPrismaError(err)
    logger.error({ err, errorId, userId: req.user?.id })
    return res.status(status).json({ error: message, errorId })
  }
  next(err)
})
## Example 1: Error Transformation Function

Pattern: P-code to User Message

import { Prisma } from '@prisma/client'

function transformPrismaError(error: unknown) {
  const errorId = crypto.randomUUID()

  if (error instanceof Prisma.PrismaClientKnownRequestError) {
    switch (error.code) {
      case 'P2002':
        return {
          status: 409,
          message: 'A record with this information already exists.',
          errorId,
          logDetails: {
            code: error.code,
            meta: error.meta,
            target: error.meta?.target
          }
        }

      case 'P2025':
        return {
          status: 404,
          message: 'The requested resource was not found.',
          errorId,
          logDetails: {
            code: error.code,
            meta: error.meta
          }
        }

      case 'P2003':
        return {
          status: 400,
          message: 'The provided reference is invalid.',
          errorId,
          logDetails: {
            code: error.code,
            meta: error.meta,
            field: error.meta?.field_name
          }
        }

      case 'P2014':
        return {
          status: 400,
          message: 'The change violates a required relationship.',
          errorId,
          logDetails: {
            code: error.code,
            meta: error.meta
          }
        }

      default:
        return {
          status: 500,
          message: 'An error occurred while processing your request.',
          errorId,
          logDetails: {
            code: error.code,
            meta: error.meta
          }
        }
    }
  }

  if (error instanceof Prisma.PrismaClientValidationError) {
    return {
      status: 400,
      message: 'The provided data is invalid.',
      errorId,
      logDetails: {
        type: 'ValidationError',
        message: error.message
      }
    }
  }

  return {
    status: 500,
    message: 'An unexpected error occurred.',
    errorId,
    logDetails: {
      type: error?.constructor?.name,
      message: error instanceof Error ? error.message : 'Unknown error'
    }
  }
}

Example 2: Production Error Handler

Pattern: Middleware with Logging

import { Prisma } from '@prisma/client'
import { logger } from './logger'

export function handlePrismaError(error: unknown, context?: Record<string, unknown>) {
  const { status, message, errorId, logDetails } = transformPrismaError(error)

  logger.error({
    errorId,
    ...logDetails,
    context,
    stack: error instanceof Error ? error.stack : undefined,
    timestamp: new Date().toISOString()
  })

  return {
    status,
    body: {
      error: message,
      errorId
    }
  }
}

export async function createUser(data: { email: string; name: string }) {
  try {
    return await prisma.user.create({ data })
  } catch (error) {
    const { status, body } = handlePrismaError(error, {
      operation: 'createUser',
      email: data.email
    })
    throw new ApiError(status, body)
  }
}

Example 3: Environment-Aware Error Handling

Pattern: Development vs Production

const isDevelopment = process.env.NODE_ENV === 'development'

function formatErrorResponse(error: unknown, errorId: string) {
  const { status, message, logDetails } = transformPrismaError(error)

  const baseResponse = {
    error: message,
    errorId
  }

  if (isDevelopment && error instanceof Prisma.PrismaClientKnownRequestError) {
    return {
      ...baseResponse,
      debug: {
        code: error.code,
        meta: error.meta,
        clientVersion: Prisma.prismaVersion.client
      }
    }
  }

  return baseResponse
}

Example 4: Specific Field Error Extraction

Pattern: P2002 Constraint Details

function extractP2002Details(error: Prisma.PrismaClientKnownRequestError) {
  if (error.code !== 'P2002') return null

  const target = error.meta?.target as string[] | undefined

  if (!target || target.length === 0) {
    return 'A record with this information already exists.'
  }

  const fieldMap: Record<string, string> = {
    email: 'email address',
    username: 'username',
    phone: 'phone number',
    slug: 'identifier'
  }

  const fieldName = target[0]
  const friendlyName = fieldMap[fieldName] || 'information'

  return `A record with this ${friendlyName} already exists.`
}

function transformPrismaError(error: unknown) {
  if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
    const message = extractP2002Details(error)
    return {
      status: 409,
      message,
      errorId: crypto.randomUUID(),
      logDetails: { code: 'P2002', target: error.meta?.target }
    }
  }
}
## Error Response Format

Client Response (JSON):

{
  "error": "User-friendly message without database details",
  "errorId": "uuid-for-correlation"
}

Server Log (Structured):

{
  "level": "error",
  "errorId": "uuid-for-correlation",
  "code": "P2002",
  "meta": { "target": ["email"] },
  "context": { "operation": "createUser", "userId": "123" },
  "stack": "Error stack trace...",
  "timestamp": "2025-11-21T10:30:00Z"
}

Development Response (Optional Debug):

{
  "error": "User-friendly message",
  "errorId": "uuid-for-correlation",
  "debug": {
    "code": "P2002",
    "meta": { "target": ["email"] },
    "clientVersion": "6.0.0"
  }
}
## Security Requirements

MUST:

  • Transform ALL Prisma errors before sending to clients
  • Log full error details server-side with correlation IDs
  • Remove P-codes from production API responses
  • Remove table/column names from client messages
  • Remove constraint names from client messages
  • Use generic messages for unexpected errors

SHOULD:

  • Include error IDs for support correlation
  • Monitor error rates for security patterns
  • Use structured logging for error analysis
  • Implement field-specific messages for P2002
  • Differentiate 404 (P2025) from 400/500 errors

NEVER:

  • Expose P-codes to clients in production
  • Include error.meta in API responses
  • Show Prisma stack traces to clients
  • Reveal table or column names
  • Display constraint names
  • Return raw error.message to clients

Common P-codes to Handle

P2002 - Unique constraint violation

  • Status: 409 Conflict
  • Message: "A record with this information already exists"

P2025 - Record not found

  • Status: 404 Not Found
  • Message: "The requested resource was not found"

P2003 - Foreign key constraint violation

  • Status: 400 Bad Request
  • Message: "The provided reference is invalid"

P2014 - Required relation violation

  • Status: 400 Bad Request
  • Message: "The change violates a required relationship"

P2024 - Connection timeout

  • Status: 503 Service Unavailable
  • Message: "Service temporarily unavailable"
  • Action: Log urgently, indicates connection pool exhaustion
## Security Checklist

After implementing error handling:

  1. Verify No P-codes Exposed:

    • Search API responses for "P20" pattern
    • Test each error scenario
    • Check production logs vs API responses
  2. Confirm Logging Works:

    • Trigger known errors (P2002, P2025)
    • Verify errorId appears in both logs and response
    • Confirm full error details in logs only
  3. Test Error Scenarios:

    • Unique constraint violation (create duplicate)
    • Not found (query non-existent record)
    • Foreign key violation (invalid reference)
    • Validation error (missing required field)
  4. Review Environment Behavior:

    • Production: No P-codes, no meta, no stack
    • Development: Optional debug info
    • Logs: Full details in both environments

Integration with SECURITY-input-validation

Error exposure prevention works with input validation:

  1. Input Validation (SECURITY-input-validation skill):

    • Validate data before Prisma operations
    • Return validation errors with field-level messages
    • Prevent malformed data reaching database
  2. Error Transformation (this skill):

    • Handle database-level errors
    • Transform Prisma errors to user messages
    • Log server-side for debugging

Pattern:

async function createUser(input: unknown) {
  const validation = userSchema.safeParse(input)

  if (!validation.success) {
    return {
      status: 400,
      body: {
        error: 'Invalid user data',
        fields: validation.error.flatten().fieldErrors
      }
    }
  }

  try {
    return await prisma.user.create({ data: validation.data })
  } catch (error) {
    const { status, body } = handlePrismaError(error)
    return { status, body }
  }
}

Validation catches format issues, error transformation handles database constraints.

Error Handling and Validation:

  • If sanitizing error messages for user display, use the sanitizing-user-inputs skill from typescript for safe error formatting
  • If customizing Zod validation errors, use the customizing-errors skill from zod-4 for user-friendly error messages