Security & Performance
Error Handling and Information Disclosure
Error Handling and Information Disclosure
Proper error handling prevents sensitive information leakage while maintaining good user experience and debugging capabilities.
Custom Error Pages
Create user-friendly error pages that don't reveal system information:
<!-- resources/views/errors/500.blade.php -->
<!DOCTYPE html>
<html>
<head>
<title>Server Error</title>
</head>
<body>
<h1>500 - Internal Server Error</h1>
<p>Something went wrong. Please try again later.</p>
<!-- NO stack traces, file paths, or system details -->
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Server Error</title>
</head>
<body>
<h1>500 - Internal Server Error</h1>
<p>Something went wrong. Please try again later.</p>
<!-- NO stack traces, file paths, or system details -->
</body>
</html>
Never display: Stack traces, file paths, database errors, version numbers, or environment details in production.
Error Logging Configuration
Configure Laravel to log errors securely:
// config/logging.php
'channels' => [
'production' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'error',
'days' => 14,
'permission' => 0640, // Restrict file permissions
],
];
'channels' => [
'production' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'error',
'days' => 14,
'permission' => 0640, // Restrict file permissions
],
];
Debug Mode in .env
Critical environment configuration:
# .env (PRODUCTION)
APP_ENV=production
APP_DEBUG=false
LOG_LEVEL=error
# .env (DEVELOPMENT)
APP_ENV=local
APP_DEBUG=true
LOG_LEVEL=debug
APP_ENV=production
APP_DEBUG=false
LOG_LEVEL=error
# .env (DEVELOPMENT)
APP_ENV=local
APP_DEBUG=true
LOG_LEVEL=debug
Security Risk: APP_DEBUG=true in production exposes: stack traces, environment variables, configuration values, file paths, and database queries.
Custom Exception Handler
Control error responses in production:
// app/Exceptions/Handler.php
public function render($request, Throwable $e)
{
if (app()->environment('production')) {
// Log full error details
Log::error($e->getMessage(), [
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
]);
// Return generic error to user
return response()->view('errors.500', [], 500);
}
return parent::render($request, $e);
}
public function render($request, Throwable $e)
{
if (app()->environment('production')) {
// Log full error details
Log::error($e->getMessage(), [
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
]);
// Return generic error to user
return response()->view('errors.500', [], 500);
}
return parent::render($request, $e);
}
Information Leakage Prevention
Common Information Leaks:
- Stack traces revealing file structure
- Database error messages showing table/column names
- Framework version in HTTP headers
- Comments in HTML source code
- Error messages revealing user existence
Secure Error Messages
Examples of safe vs unsafe error messages:
// ❌ BAD: Reveals user existence
return 'Email already exists in database';
// ✅ GOOD: Generic message
return 'Registration failed. Please try again.';
// ❌ BAD: Reveals file path
throw new Exception('File not found: /var/www/uploads/secret.pdf');
// ✅ GOOD: Generic message
throw new Exception('File not found');
return 'Email already exists in database';
// ✅ GOOD: Generic message
return 'Registration failed. Please try again.';
// ❌ BAD: Reveals file path
throw new Exception('File not found: /var/www/uploads/secret.pdf');
// ✅ GOOD: Generic message
throw new Exception('File not found');
Error Boundaries in Frontend
React error boundary example:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log to error tracking service
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log to error tracking service
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}
Security Headers
Hide server information:
// app/Http/Middleware/SecurityHeaders.php
public function handle($request, Closure $next)
{
$response = $next($request);
// Remove server signature
$response->headers->remove('X-Powered-By');
$response->headers->set('Server', 'WebServer');
return $response;
}
public function handle($request, Closure $next)
{
$response = $next($request);
// Remove server signature
$response->headers->remove('X-Powered-By');
$response->headers->set('Server', 'WebServer');
return $response;
}
Best Practice: Implement centralized error handling, use structured logging, monitor error rates, and never expose internal details to end users.
Exercise: Review your application for information disclosure vulnerabilities. Check error pages, API responses, and log files. Set APP_DEBUG=false and test all error scenarios.