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

484 lines
12 KiB
Markdown

---
name: preventing-error-exposure
description: Prevent leaking database errors and P-codes to clients. Use when implementing API error handling or user-facing error messages.
allowed-tools: Read, Write, Edit
version: 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.
---
<role>
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.
</role>
<when-to-activate>
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
</when-to-activate>
<overview>
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
</overview>
<workflow>
## 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
</workflow>
<conditional-workflows>
## 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:**
```typescript
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:**
```typescript
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)
})
```
</conditional-workflows>
<examples>
## Example 1: Error Transformation Function
**Pattern: P-code to User Message**
```typescript
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**
```typescript
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**
```typescript
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**
```typescript
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 }
}
}
}
```
</examples>
<output-format>
## Error Response Format
**Client Response (JSON):**
```json
{
"error": "User-friendly message without database details",
"errorId": "uuid-for-correlation"
}
```
**Server Log (Structured):**
```json
{
"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):**
```json
{
"error": "User-friendly message",
"errorId": "uuid-for-correlation",
"debug": {
"code": "P2002",
"meta": { "target": ["email"] },
"clientVersion": "6.0.0"
}
}
```
</output-format>
<constraints>
## 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
</constraints>
<validation>
## 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
</validation>
---
## 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:**
```typescript
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.
## Related Skills
**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