Real-World Examples
Learn how to use Cloak through real-world scenarios and examples.
Database Connection Leaks
Section titled “Database Connection Leaks”Problem
Section titled “Problem”Database exceptions often expose connection strings:
// ❌ Without CloakSQLSTATE[HY000] [2002] Connection failed: mysql://root:mySecretP@ss@db-prod.company.com/production_dbSolution
Section titled “Solution”// ✅ With CloakA database error occurred while processing your request.Configuration
Section titled “Configuration”'sanitize_exceptions' => [ \Illuminate\Database\QueryException::class, \PDOException::class,],
'generic_messages' => [ \Illuminate\Database\QueryException::class => 'A database error occurred while processing your request.',],API Key Exposure
Section titled “API Key Exposure”Problem
Section titled “Problem”API client exceptions can leak credentials:
// ❌ Without CloakHTTP 401: Invalid API key: prod_abc123def456ghi789jklBearer: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example.tokenSolution
Section titled “Solution”// ✅ With CloakHTTP 401: Invalid API key: [REDACTED]Bearer: [REDACTED]Configuration
Section titled “Configuration”'patterns' => [ '/api[_-]?key["\']?\s*[:=]\s*["\']?([a-zA-Z0-9_\-]+)/i', '/bearer\s+([a-zA-Z0-9_\-\.]+)/i',],AWS Credentials in Logs
Section titled “AWS Credentials in Logs”Problem
Section titled “Problem”Cloud service errors can expose AWS credentials:
// ❌ Without CloakAWS Error: Invalid credentialsAWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLEAWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYSolution
Section titled “Solution”// ✅ With CloakAWS Error: Invalid credentialsAWS_ACCESS_KEY_ID=[REDACTED]AWS_SECRET_ACCESS_KEY=[REDACTED]Configuration
Section titled “Configuration”'patterns' => [ '/aws[_-]?access[_-]?key[_-]?id["\']?\s*[:=]\s*["\']?([A-Z0-9]+)/i', '/aws[_-]?secret[_-]?access[_-]?key["\']?\s*[:=]\s*["\']?([A-Za-z0-9\/\+]+)/i',],
'sanitize_exceptions' => [ \Aws\Exception\AwsException::class,],File Path Disclosure
Section titled “File Path Disclosure”Problem
Section titled “Problem”File operations can reveal system paths:
// ❌ Without CloakError opening file: /Users/john.doe/projects/acme-corp/storage/app/secrets.jsonSolution
Section titled “Solution”// ✅ With CloakError opening file: /Users/[REDACTED]/projects/acme-corp/storage/app/secrets.jsonConfiguration
Section titled “Configuration”'patterns' => [ '/\/Users\/([^\/\s]+)/i', '/\/home\/([^\/\s]+)/i', '/C:\\\\Users\\\\([^\\\\]+)/i',],Multi-Tenant Application
Section titled “Multi-Tenant Application”Problem
Section titled “Problem”Tenant-specific data in exceptions:
// ❌ Without CloakQuery failed for tenant 'acme_corp' using database: acme_prod_dbConnection: mysql://acme_user:acmePass2024@mysql-acme.internal:3306/acme_prod_dbSolution
Section titled “Solution”Create tenant-aware sanitization:
use Cline\Cloak\Facades\Cloak;
class TenantExceptionHandler{ public function render($request, Throwable $e) { $sanitized = Cloak::sanitizeForRendering($e, $request);
// Additional tenant sanitization $tenant = $request->tenant(); $message = str_replace( [$tenant->name, $tenant->database], ['[TENANT]', '[DATABASE]'], $sanitized->getMessage() );
return response()->json(['error' => $message], 500); }}Result
Section titled “Result”// ✅ SanitizedQuery failed for tenant '[TENANT]' using database: [DATABASE]Connection: [REDACTED]Email in Exception Messages
Section titled “Email in Exception Messages”Problem
Section titled “Problem”User emails in error messages:
// ❌ Without CloakUser john.doe@company.com not found in systemEmail validation failed for admin@internal-company-domain.comSolution
Section titled “Solution”// ✅ With CloakUser [REDACTED] not found in systemEmail validation failed for [REDACTED]Configuration
Section titled “Configuration”'patterns' => [ '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/',],Development vs Production
Section titled “Development vs Production”Problem
Section titled “Problem”Need full details in development, sanitized in production:
Solution
Section titled “Solution”Environment-specific configuration:
return [ 'enabled' => env('CLOAK_ENABLED', app()->environment('production')),
'sanitize_in_debug' => env('CLOAK_SANITIZE_IN_DEBUG', false),
'patterns' => app()->environment('production') ? [ // Aggressive sanitization in production '/mysql:\/\//i', '/postgres:\/\//i', '/password/i', '/secret/i', '/token/i', '/api[_-]?key/i', ] : [ // Minimal sanitization in development '/password=([^\s;]+)/i', ],];Logging Original Exceptions
Section titled “Logging Original Exceptions”Problem
Section titled “Problem”Need to debug while showing sanitized messages:
Solution
Section titled “Solution”Cloak automatically logs originals:
'log_original' => true,Then in logs:
// Log entry[2024-01-15 10:30:45] production.ERROR: Original exception before sanitization{ "exception": "PDOException", "message": "SQLSTATE[HY000]: mysql://root:secret@localhost/db", "file": "/var/www/app/Services/DatabaseService.php", "line": 42, "url": "https://api.example.com/users", "method": "GET"}API vs Web Responses
Section titled “API vs Web Responses”Problem
Section titled “Problem”Different sanitization needs for API vs web:
Solution
Section titled “Solution”use Cline\Cloak\Facades\Cloak;
->withExceptions(function (Exceptions $exceptions) { $exceptions->render(function (Throwable $e, Request $request) { // API routes get aggressive sanitization if ($request->is('api/*')) { config(['cloak.sanitize_in_debug' => true]); $e = Cloak::sanitizeForRendering($e, $request);
return response()->json([ 'error' => $e->getMessage(), ], 500); }
// Web routes get normal sanitization return Cloak::sanitizeForRendering($e, $request); });})Custom Exception Types
Section titled “Custom Exception Types”Problem
Section titled “Problem”Your custom exceptions need sanitization:
Solution
Section titled “Solution”namespace App\Exceptions;
use RuntimeException;
class PaymentException extends RuntimeException{ public function __construct( string $message, public readonly string $transactionId, public readonly string $gatewayResponse, ) { parent::__construct($message); }}Configure Cloak to sanitize it:
'sanitize_exceptions' => [ \App\Exceptions\PaymentException::class,],
'generic_messages' => [ \App\Exceptions\PaymentException::class => 'A payment processing error occurred. Please try again.',],Stack Trace Sanitization
Section titled “Stack Trace Sanitization”Problem
Section titled “Problem”Stack traces can leak file paths and function arguments:
// ❌ Without sanitization#0 /home/production-user/apps/secret-project/app/Services/PaymentService.php(42): PaymentService->processPayment('secret-api-key', 'password123')#1 /home/production-user/apps/secret-project/app/Controllers/PaymentController.php(100)Solution
Section titled “Solution”Cloak automatically sanitizes stack traces when enabled:
return [ 'sanitize_stack_traces' => true,
'patterns' => [ '/\/home\/([^\/]+)/i', '/\/Users\/([^\/]+)/i', ],];Result:
// ✅ Sanitized#0 /home/[REDACTED]/apps/secret-project/app/Services/PaymentService.php(42): PaymentService->processPayment()#1 /home/[REDACTED]/apps/secret-project/app/Controllers/PaymentController.php(100)Cloak automatically:
- Applies your configured patterns to file paths
- Omits function arguments to prevent leaking sensitive data passed as parameters
- Preserves class and function names for debugging
- Preserves line numbers
Testing Sanitization
Section titled “Testing Sanitization”Test your configuration:
use Cline\Cloak\CloakManager;
test('sanitizes production database errors', function () { config([ 'cloak.enabled' => true, 'cloak.sanitize_exceptions' => [QueryException::class], 'cloak.generic_messages' => [ QueryException::class => 'Database error occurred.', ], ]);
$pdo = new PDOException('mysql://root:secret@prod-db/app'); $exception = new QueryException('default', 'SELECT *', [], $pdo);
$manager = app(CloakManager::class); $sanitized = $manager->sanitizeForRendering($exception);
expect($sanitized->getMessage()) ->toBe('Database error occurred.') ->not->toContain('secret') ->not->toContain('prod-db');});Error ID Tracking with Nightwatch
Section titled “Error ID Tracking with Nightwatch”Problem
Section titled “Problem”When customers report errors, you need to correlate their reports with server logs:
// Customer: "I got an error when checking out"// You: "Which error? When? What page?"// 🤷 Hard to find the specific exceptionSolution
Section titled “Solution”Enable error ID tracking:
CLOAK_ERROR_ID_TYPE=uuidCLOAK_ERROR_ID_CONTEXT_KEY=exception_id// ✅ Customer sees:A database error occurred. [Error ID: 87ccc529-0646-4d06-a5b8-4137a88fb405]
// ✅ You search Nightwatch for: exception_id:87ccc529// Find the original exception with full detailsConfiguration
Section titled “Configuration”return [ // Generate UUID for each sanitized exception 'error_id_type' => 'uuid', // or 'ulid', or null
// Include ID in message 'error_id_template' => '{message} [Error ID: {id}]',
// Store in Context for Nightwatch 'error_id_context_key' => 'exception_id',];Accessing Error IDs
Section titled “Accessing Error IDs”try { // ... code that throws} catch (Throwable $e) { $sanitized = Cloak::sanitizeForRendering($e);
// Get the error ID if ($sanitized instanceof SanitizedException) { $errorId = $sanitized->getErrorId();
// Show to user, include in support emails, etc. return response()->json([ 'error' => $sanitized->getMessage(), 'error_id' => $errorId, ], 500); }}Laravel Context automatically includes the error ID in all logs and monitoring tools like Nightwatch.
Custom Context Injection
Section titled “Custom Context Injection”Problem
Section titled “Problem”You need additional context data (user ID, tenant ID, request ID) logged with exceptions for debugging:
// ❌ Missing context[2024-01-15 10:30:45] production.ERROR: Database connection failed// Which user? Which tenant? Hard to track down!Solution
Section titled “Solution”Configure context callbacks to automatically inject data:
return [ 'context' => [ 'user_id' => fn () => auth()->id(), 'tenant_id' => fn () => tenant()?->id, 'request_id' => fn () => request()->header('X-Request-ID'), 'ip_address' => fn () => request()->ip(), ],];These callbacks execute when an exception is sanitized, and results are stored in Laravel Context for logging and monitoring.
Result
Section titled “Result”// ✅ Full context in logs[2024-01-15 10:30:45] production.ERROR: Database connection failed{ "exception_id": "87ccc529-0646-4d06-a5b8-4137a88fb405", "user_id": 123, "tenant_id": "acme-corp", "request_id": "req_abc123", "ip_address": "192.168.1.100"}Important notes:
- Callbacks that throw exceptions are silently ignored (won’t break sanitization)
- Callbacks returning
nullare skipped - All context is automatically available to Nightwatch and other monitoring tools
Exception Tags and Categories
Section titled “Exception Tags and Categories”Problem
Section titled “Problem”Need to categorize exceptions for filtering and alerting:
// ❌ No categorization// All exceptions look the same in monitoring// Can't filter by severity or typeSolution
Section titled “Solution”Tag exceptions by class for automatic categorization:
return [ 'tags' => [ \Illuminate\Database\QueryException::class => ['database', 'critical'], \PDOException::class => ['database', 'critical'], \Stripe\Exception\CardException::class => ['payment', 'third-party'], \Stripe\Exception\ApiErrorException::class => ['payment', 'third-party', 'critical'], \App\Exceptions\RateLimitException::class => ['rate-limit', 'warning'], ],];Tags are automatically added to Laravel Context as exception_tags.
Result
Section titled “Result”In Nightwatch or logs:
{ "exception_id": "87ccc529-0646-4d06-a5b8-4137a88fb405", "exception_tags": ["database", "critical"], "user_id": 123}Use cases:
- Filter Nightwatch by
exception_tags:criticalto see only critical errors - Set up alerts for specific categories (e.g., alert on
payment+critical) - Group exceptions by type in dashboards
- Route errors to different teams based on tags
API Error Responses
Section titled “API Error Responses”Problem
Section titled “Problem”Inconsistent error response formats across API endpoints and no support for industry-standard API formats:
// ❌ InconsistentRoute::post('/users', function () { try { // ... } catch (Throwable $e) { return response()->json(['message' => $e->getMessage()], 500); }});
Route::post('/orders', function () { try { // ... } catch (Throwable $e) { return response()->json(['error' => $e->getMessage()], 500); }});Solution
Section titled “Solution”Cloak supports all formats from API Platform:
- Simple - Basic JSON (default)
- JSON:API - JSON:API specification
- Problem+JSON - RFC 7807
- HAL - Hypertext Application Language
- Hydra - JSON-LD + Hydra
Configure globally:
// config/cloak.php or .env'error_response_format' => 'json-api', // or problem-json, hal, hydra, simpleOr specify per-response:
use Cline\Cloak\Facades\Cloak;
->withExceptions(function (Exceptions $exceptions) { $exceptions->render(function (Throwable $e, Request $request) { if ($request->is('api/*')) { return Cloak::toJsonResponse( exception: $e, request: $request, format: 'json-api' // Override default ); }
return Cloak::sanitizeForRendering($e, $request); });})Format Examples
Section titled “Format Examples”Simple (default):
{ "error": "A database error occurred.", "error_id": "87ccc529-0646-4d06-a5b8-4137a88fb405"}JSON:API:
{ "errors": [{ "id": "87ccc529-0646-4d06-a5b8-4137a88fb405", "status": "500", "title": "Internal Server Error", "detail": "A database error occurred." }]}Problem+JSON (RFC 7807):
{ "type": "about:blank", "title": "Internal Server Error", "status": 500, "detail": "A database error occurred.", "instance": "urn:uuid:87ccc529-0646-4d06-a5b8-4137a88fb405"}HAL:
{ "message": "A database error occurred.", "status": 500, "error_id": "87ccc529-0646-4d06-a5b8-4137a88fb405", "_links": { "self": {"href": "/api/users"} }}Hydra (JSON-LD):
{ "@context": "/contexts/Error", "@type": "hydra:Error", "@id": "urn:uuid:87ccc529-0646-4d06-a5b8-4137a88fb405", "hydra:title": "Internal Server Error", "hydra:description": "A database error occurred."}Custom Formatters
Section titled “Custom Formatters”Create your own formatter:
namespace App\Http\Formatters;
use Cline\Cloak\Contracts\ResponseFormatter;use Illuminate\Http\JsonResponse;use Throwable;
class MyCustomFormatter implements ResponseFormatter{ public function format( Throwable $exception, int $status = 500, bool $includeTrace = false, array $headers = [], ): JsonResponse { $data = [ 'success' => false, 'message' => $exception->getMessage(), 'code' => $status, ];
return new JsonResponse($data, $status, $headers); }
public function getContentType(): string { return 'application/vnd.myapi+json'; }}Register it:
'custom_formatters' => [ 'my-format' => \App\Http\Formatters\MyCustomFormatter::class,],
// Use itCloak::toJsonResponse($exception, format: 'my-format');Next Steps
Section titled “Next Steps”- Review security best practices
- Learn about custom patterns
- Explore exception handling strategies