error-sanitization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Error Sanitization

错误信息脱敏

Production-safe error handling: log everything server-side, expose nothing sensitive to users.
面向生产环境的安全错误处理:在服务器端记录所有错误详情,不向用户暴露任何敏感信息。

When to Use This Skill

适用场景

  • Building APIs that return error messages to clients
  • Handling exceptions in production environments
  • Processing batch operations with partial failures
  • Any system where error messages could leak sensitive information
  • 开发向客户端返回错误信息的API
  • 在生产环境中处理异常
  • 处理存在部分失败情况的批量操作
  • 任何可能通过错误信息泄露敏感数据的系统

Core Concepts

核心概念

Error messages can leak sensitive information including database connection strings, internal file paths, stack traces, API keys, and business logic details. The solution is to always log full error details server-side for debugging while returning only generic, safe messages to users.
The flow is:
  1. Exception occurs
  2. Log FULL error server-side with context
  3. Classify error type
  4. Return GENERIC message to user
  5. Only expose safe, actionable errors (like validation)
错误信息可能泄露敏感数据,包括数据库连接字符串、内部文件路径、堆栈跟踪信息、API密钥及业务逻辑细节。解决方案是:始终在服务器端记录完整的错误详情以供调试,同时仅向用户返回通用、安全的提示信息。
流程如下:
  1. 发生异常
  2. 在服务器端记录包含上下文的完整错误信息
  3. 对错误类型进行分类
  4. 向用户返回通用提示信息
  5. 仅暴露安全、可操作的错误(如验证错误)

Implementation

实现方案

Python

Python

python
import os
import logging
from typing import Optional
from fastapi import HTTPException
from pydantic import ValidationError

logger = logging.getLogger(__name__)


class ErrorSanitizer:
    """
    Sanitizes error messages to prevent information leakage.
    """
    
    SENSITIVE_PATTERNS = [
        "password", "secret", "key", "token", "credential",
        "postgresql://", "mysql://", "mongodb://", "redis://",
        "localhost", "127.0.0.1", "internal", "0.0.0.0",
        "traceback", "exception", "error at", "line ",
        "/home/", "/var/", "/etc/", "C:\\",
        "SUPABASE", "AWS", "STRIPE", "SENDGRID",
    ]
    
    @staticmethod
    def is_production() -> bool:
        return os.getenv("ENVIRONMENT", "development").lower() == "production"
    
    @staticmethod
    def sanitize_error(
        e: Exception,
        user_message: str = "Operation failed",
        log_context: Optional[dict] = None
    ) -> str:
        """Sanitize error message for user display."""
        # ALWAYS log full error server-side
        logger.error(
            f"Error occurred: {type(e).__name__}: {str(e)}",
            exc_info=True,
            extra=log_context or {}
        )
        
        # In development, show more details
        if not ErrorSanitizer.is_production():
            return f"{user_message}: {str(e)}"
        
        # Validation errors are safe (user input issues)
        if isinstance(e, ValidationError):
            return f"Validation error: {str(e)}"
        
        # HTTPException with client error (4xx) is safe
        if isinstance(e, HTTPException):
            if 400 <= e.status_code < 500:
                return e.detail
            return user_message
        
        # Check for sensitive patterns
        error_str = str(e).lower()
        for pattern in ErrorSanitizer.SENSITIVE_PATTERNS:
            if pattern in error_str:
                return user_message
        
        # Short, simple errors without sensitive patterns might be safe
        if len(str(e)) < 100 and not any(c in str(e) for c in ['/', '\\', '@', ':']):
            return str(e)
        
        return user_message
    
    @staticmethod
    def create_http_exception(
        e: Exception,
        status_code: int = 500,
        user_message: str = "Operation failed",
        log_context: Optional[dict] = None
    ) -> HTTPException:
        """Create HTTPException with sanitized error message."""
        safe_message = ErrorSanitizer.sanitize_error(e, user_message, log_context)
        return HTTPException(status_code=status_code, detail=safe_message)
python
import os
import logging
from typing import Optional
from fastapi import HTTPException
from pydantic import ValidationError

logger = logging.getLogger(__name__)


class ErrorSanitizer:
    """
    Sanitizes error messages to prevent information leakage.
    """
    
    SENSITIVE_PATTERNS = [
        "password", "secret", "key", "token", "credential",
        "postgresql://", "mysql://", "mongodb://", "redis://",
        "localhost", "127.0.0.1", "internal", "0.0.0.0",
        "traceback", "exception", "error at", "line ",
        "/home/", "/var/", "/etc/", "C:\\",
        "SUPABASE", "AWS", "STRIPE", "SENDGRID",
    ]
    
    @staticmethod
    def is_production() -> bool:
        return os.getenv("ENVIRONMENT", "development").lower() == "production"
    
    @staticmethod
    def sanitize_error(
        e: Exception,
        user_message: str = "Operation failed",
        log_context: Optional[dict] = None
    ) -> str:
        """Sanitize error message for user display."""
        # ALWAYS log full error server-side
        logger.error(
            f"Error occurred: {type(e).__name__}: {str(e)}",
            exc_info=True,
            extra=log_context or {}
        )
        
        # In development, show more details
        if not ErrorSanitizer.is_production():
            return f"{user_message}: {str(e)}"
        
        # Validation errors are safe (user input issues)
        if isinstance(e, ValidationError):
            return f"Validation error: {str(e)}"
        
        # HTTPException with client error (4xx) is safe
        if isinstance(e, HTTPException):
            if 400 <= e.status_code < 500:
                return e.detail
            return user_message
        
        # Check for sensitive patterns
        error_str = str(e).lower()
        for pattern in ErrorSanitizer.SENSITIVE_PATTERNS:
            if pattern in error_str:
                return user_message
        
        # Short, simple errors without sensitive patterns might be safe
        if len(str(e)) < 100 and not any(c in str(e) for c in ['/', '\\', '@', ':']):
            return str(e)
        
        return user_message
    
    @staticmethod
    def create_http_exception(
        e: Exception,
        status_code: int = 500,
        user_message: str = "Operation failed",
        log_context: Optional[dict] = None
    ) -> HTTPException:
        """Create HTTPException with sanitized error message."""
        safe_message = ErrorSanitizer.sanitize_error(e, user_message, log_context)
        return HTTPException(status_code=status_code, detail=safe_message)

TypeScript

TypeScript

typescript
import { Logger } from './logger';

const SENSITIVE_PATTERNS = [
  'password', 'secret', 'key', 'token', 'credential',
  'postgresql://', 'mysql://', 'mongodb://', 'redis://',
  'localhost', '127.0.0.1', 'internal', '0.0.0.0',
  '/home/', '/var/', '/etc/', 'C:\\',
];

interface SanitizeOptions {
  userMessage?: string;
  logContext?: Record<string, unknown>;
}

export class ErrorSanitizer {
  private static isProduction(): boolean {
    return process.env.NODE_ENV === 'production';
  }

  static sanitize(
    error: Error,
    options: SanitizeOptions = {}
  ): string {
    const { userMessage = 'Operation failed', logContext = {} } = options;

    // Always log full error server-side
    Logger.error('Error occurred', {
      name: error.name,
      message: error.message,
      stack: error.stack,
      ...logContext,
    });

    // In development, show more details
    if (!this.isProduction()) {
      return `${userMessage}: ${error.message}`;
    }

    // Check for sensitive patterns
    const errorStr = error.message.toLowerCase();
    for (const pattern of SENSITIVE_PATTERNS) {
      if (errorStr.includes(pattern)) {
        return userMessage;
      }
    }

    // Short, simple errors might be safe
    if (error.message.length < 100 && !/[\/\\@:]/.test(error.message)) {
      return error.message;
    }

    return userMessage;
  }

  static createHttpError(
    error: Error,
    statusCode: number = 500,
    options: SanitizeOptions = {}
  ): { statusCode: number; message: string } {
    return {
      statusCode,
      message: this.sanitize(error, options),
    };
  }
}
typescript
import { Logger } from './logger';

const SENSITIVE_PATTERNS = [
  'password', 'secret', 'key', 'token', 'credential',
  'postgresql://', 'mysql://', 'mongodb://', 'redis://',
  'localhost', '127.0.0.1', 'internal', '0.0.0.0',
  '/home/', '/var/', '/etc/', 'C:\\',
];

interface SanitizeOptions {
  userMessage?: string;
  logContext?: Record<string, unknown>;
}

export class ErrorSanitizer {
  private static isProduction(): boolean {
    return process.env.NODE_ENV === 'production';
  }

  static sanitize(
    error: Error,
    options: SanitizeOptions = {}
  ): string {
    const { userMessage = 'Operation failed', logContext = {} } = options;

    // Always log full error server-side
    Logger.error('Error occurred', {
      name: error.name,
      message: error.message,
      stack: error.stack,
      ...logContext,
    });

    // In development, show more details
    if (!this.isProduction()) {
      return `${userMessage}: ${error.message}`;
    }

    // Check for sensitive patterns
    const errorStr = error.message.toLowerCase();
    for (const pattern of SENSITIVE_PATTERNS) {
      if (errorStr.includes(pattern)) {
        return userMessage;
      }
    }

    // Short, simple errors might be safe
    if (error.message.length < 100 && !/[\/\\@:]/.test(error.message)) {
      return error.message;
    }

    return userMessage;
  }

  static createHttpError(
    error: Error,
    statusCode: number = 500,
    options: SanitizeOptions = {}
  ): { statusCode: number; message: string } {
    return {
      statusCode,
      message: this.sanitize(error, options),
    };
  }
}

Usage Examples

使用示例

Route Handler

路由处理器

python
@router.post("/process")
async def process_invoice(invoice_id: str):
    try:
        result = await processor.process(invoice_id)
        return result
    except ValidationError as e:
        # Validation errors are safe to expose
        raise HTTPException(status_code=400, detail=str(e))
    except HTTPException:
        raise  # Re-raise as-is
    except Exception as e:
        # All other errors get sanitized
        raise ErrorSanitizer.create_http_exception(
            e,
            status_code=500,
            user_message="Failed to process invoice",
            log_context={"invoice_id": invoice_id}
        )
python
@router.post("/process")
async def process_invoice(invoice_id: str):
    try:
        result = await processor.process(invoice_id)
        return result
    except ValidationError as e:
        # Validation errors are safe to expose
        raise HTTPException(status_code=400, detail=str(e))
    except HTTPException:
        raise  # Re-raise as-is
    except Exception as e:
        # All other errors get sanitized
        raise ErrorSanitizer.create_http_exception(
            e,
            status_code=500,
            user_message="Failed to process invoice",
            log_context={"invoice_id": invoice_id}
        )

Domain-Specific Sanitizers

领域特定脱敏器

python
def sanitize_database_error(e: Exception) -> str:
    return ErrorSanitizer.sanitize_error(
        e,
        user_message="Database operation failed. Please try again.",
        log_context={"error_type": "database"}
    )

def sanitize_api_error(e: Exception, service_name: str = "external service") -> str:
    return ErrorSanitizer.sanitize_error(
        e,
        user_message=f"Failed to communicate with {service_name}.",
        log_context={"error_type": "external_api", "service": service_name}
    )
python
def sanitize_database_error(e: Exception) -> str:
    return ErrorSanitizer.sanitize_error(
        e,
        user_message="Database operation failed. Please try again.",
        log_context={"error_type": "database"}
    )

def sanitize_api_error(e: Exception, service_name: str = "external service") -> str:
    return ErrorSanitizer.sanitize_error(
        e,
        user_message=f"Failed to communicate with {service_name}.",
        log_context={"error_type": "external_api", "service": service_name}
    )

Batch Operations with Partial Failures

存在部分失败的批量操作

python
def process_items(items: list[dict]) -> dict:
    failed_items = []
    
    for idx, item in enumerate(items):
        try:
            process_item(item)
        except Exception as e:
            error_type = classify_error(e)
            failed_items.append({
                "line": idx,
                "description": item['description'][:50],  # Truncate
                "error_type": error_type,
                "message": get_user_friendly_message(error_type)
                # NOTE: Don't include str(e) - might be sensitive
            })
    
    return {
        "status": "partial_success" if failed_items else "success",
        "failed_items": failed_items
    }
python
def process_items(items: list[dict]) -> dict:
    failed_items = []
    
    for idx, item in enumerate(items):
        try:
            process_item(item)
        except Exception as e:
            error_type = classify_error(e)
            failed_items.append({
                "line": idx,
                "description": item['description'][:50],  # Truncate
                "error_type": error_type,
                "message": get_user_friendly_message(error_type)
                # NOTE: Don't include str(e) - might be sensitive
            })
    
    return {
        "status": "partial_success" if failed_items else "success",
        "failed_items": failed_items
    }

Best Practices

最佳实践

  1. Always log full error details server-side with
    exc_info=True
  2. Include correlation IDs (request_id, session_id) in logs for tracing
  3. Truncate user input in error messages to prevent log injection
  4. Test error handling in production mode - errors look different in dev vs prod
  5. Monitor "unknown" error rates - high rates indicate missing classification
  1. 始终在服务器端使用
    exc_info=True
    记录完整错误详情
  2. 在日志中包含关联ID(request_id、session_id)以便追踪
  3. 截断错误信息中的用户输入,防止日志注入
  4. 在生产模式下测试错误处理——开发环境与生产环境的错误表现不同
  5. 监控“未知”错误的发生率——高发生率表明存在未分类的错误类型

Common Mistakes

常见误区

  • Exposing raw exception messages to users in production
  • Forgetting to log the full error before sanitizing
  • Including sensitive data in log messages (passwords, connection strings)
  • Not testing error responses in production mode
  • Trusting that "safe" errors don't contain injected content
  • 在生产环境中向用户暴露原始异常信息
  • 在脱敏前忘记记录完整错误信息
  • 在日志中包含敏感数据(密码、连接字符串)
  • 未在生产模式下测试错误响应
  • 轻信“安全”错误不包含注入内容

Related Patterns

相关模式

  • exception-taxonomy - Hierarchical exception system with error codes
  • circuit-breaker - Prevent cascading failures
  • error-handling - General error handling patterns
  • exception-taxonomy - 带错误码的分层异常系统
  • circuit-breaker - 防止级联故障
  • error-handling - 通用错误处理模式