Comprehensive security documentation for Laravel Exception Notifier package.
The Laravel Exception Notifier package is designed with security-first principles. This document outlines security measures, best practices, and vulnerability reporting procedures.
Problem: Exception in exception handler = infinite loop + application crash
Solution: Multi-layer protection
// Layer 1: Try-catch wrapper
try {
$this->notifierService->notify($e);
} catch (Throwable $notificationError) {
// Silently fail, never crash the app
Log::error('Exception notification failed', [
'error' => $notificationError->getMessage(),
]);
}Guarantees:
- ✅ Notification failures never break your application
- ✅ Original exception always logged
- ✅ No infinite loops
- ✅ No cascading failures
Problem: Manual instantiation can miss dependencies
Solution: Laravel service container auto-resolution
// ❌ WRONG - Missing dependencies
return (new JsonExceptionHandler)->handle($e);
// ✅ CORRECT - Auto-resolves dependencies
return app(JsonExceptionHandler::class)->handle($e);Benefits:
- ✅ Type-safe dependency injection
- ✅ Automatic dependency resolution
- ✅ No missing constructor parameters
- ✅ Testable architecture
Problem: Exception flood = email spam + SMTP ban
Solution: Per-signature rate limiting
// Configurable rate limits per unique exception
'max_emails_per_signature_per_hour' => 10,
'rate_limit_window' => 3600, // 1 hourProtection Against:
- ✅ Email spam attacks
- ✅ SMTP service bans
- ✅ Denial of Service (DoS)
- ✅ Resource exhaustion
Problem: Bot traffic triggers false alarms
Solution: Automatic bot detection
'ignored_bots' => [
'googlebot', 'bingbot', 'slurp', 'crawler', 'spider',
'bot', 'facebookexternalhit', 'twitterbot', 'whatsapp',
'telegram', 'curl', 'wget', // CLI tools
],Benefits:
- ✅ Reduces false positives
- ✅ Saves email quota
- ✅ Focuses on real issues
- ✅ Prevents bot-triggered spam
Problem: Stack traces may contain passwords, tokens, API keys
Solution: Configurable context inclusion
// Control what data is included in emails
'include_request_data' => true, // URL, method, IP
'include_user_data' => true, // User ID, email
'include_stack_trace' => true, // Call stack
'max_stack_trace_depth' => 10, // Limit depthBest Practice:
// Production: Limit sensitive data
'include_request_data' => false, // Disable request body
'include_stack_trace' => true, // Keep for debugging
'max_stack_trace_depth' => 5, // Reduce depthProblem: Development spam vs production monitoring
Solution: Silent mode in local environment
// Automatic environment detection
'silent_in_local' => env('EXCEPTION_EMAIL_SILENT_LOCAL', true),Behavior:
- Local: No emails sent (logs only)
- Staging: Emails to dev team
- Production: Emails to admins
Problem: Emails sent to wrong addresses or exposed in code
Solution: Environment-based configuration
// .env file (never committed)
EXCEPTION_EMAIL_TO=admin@example.com,dev@example.com
// Fallback in config (safe for version control)
'fallback_recipients' => ['fallback@example.com'],Security Rules:
- ✅ Never hardcode email addresses
- ✅ Use environment variables
- ✅ Validate email format
- ✅ Support multiple recipients
# .env (production)
APP_ENV=production
APP_DEBUG=false
LOG_LEVEL=error
EXCEPTION_EMAIL_ENABLED=true
EXCEPTION_EMAIL_SILENT_LOCAL=true
EXCEPTION_EMAIL_TO=admin@example.com,security@example.com
EXCEPTION_EMAIL_MAX_PER_HOUR=10
MAIL_ENCRYPTION=tls
MAIL_PORT=587# ❌ DON'T DO THIS
APP_DEBUG=true # Exposes stack traces to users
EXCEPTION_EMAIL_ENABLED=false # Miss critical errors
EXCEPTION_EMAIL_SILENT_LOCAL=false # Spam during development
MAIL_ENCRYPTION=null # Unencrypted email// In your exception handler or custom logic
public function handle(Throwable $e)
{
// Sanitize exception message
$sanitized = $this->sanitizeMessage($e->getMessage());
// Create sanitized exception
$cleanException = new Exception($sanitized, $e->getCode(), $e);
// Notify with sanitized version
app(JsonExceptionHandler::class)->handle($cleanException);
}
private function sanitizeMessage(string $message): string
{
// Remove passwords, tokens, API keys
$patterns = [
'/password["\']?\s*[:=]\s*["\']?[^"\'\s]+/i',
'/token["\']?\s*[:=]\s*["\']?[^"\'\s]+/i',
'/api[_-]?key["\']?\s*[:=]\s*["\']?[^"\'\s]+/i',
];
return preg_replace($patterns, '[REDACTED]', $message);
}- Passwords (plain or hashed)
- API keys and secrets
- Credit card numbers
- Social Security Numbers
- Private keys
- Session tokens
- OAuth tokens
// app/Providers/AuthServiceProvider.php
Gate::define('manage-exceptions', function ($user) {
return $user->isAdmin();
});
// In commands (if you add auth)
if (!Gate::allows('manage-exceptions')) {
$this->error('Unauthorized');
return 1;
}# Only allow admins to clear rate limits
php artisan exception:clear-rate-limits # Requires admin accessMAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your-username
MAIL_PASSWORD=your-strong-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@example.com- ✅ Mailgun (recommended)
- ✅ Amazon SES (recommended)
- ✅ SendGrid (recommended)
- ✅ Postmark (recommended)
⚠️ Generic SMTP (ensure TLS)
# ❌ DON'T DO THIS
MAIL_ENCRYPTION=null # Unencrypted email
MAIL_PORT=25 # Unencrypted SMTP
MAIL_USERNAME=null # No authenticationREDIS_PASSWORD=your-strong-random-password
REDIS_HOST=127.0.0.1
REDIS_PORT=6379// config/cache.php
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
'encrypt' => true, // Enable encryption
],
],// In production
APP_DEBUG=false
// Users see generic error, not stack traces
return response()->json([
'message' => 'An error occurred. Please try again later.',
], 500);{{-- Don't include full stack trace in production emails --}}
@if(app()->environment('production'))
{{-- Show only first 3 frames --}}
@foreach(array_slice($data['stack_trace'], 0, 3) as $frame)
...
@endforeach
@else
{{-- Development: show full trace --}}
@foreach($data['stack_trace'] as $frame)
...
@endforeach
@endif// config/exception_notifier.php
'ignored_exceptions' => [
// User-triggered exceptions (not system errors)
\Illuminate\Validation\ValidationException::class,
\Illuminate\Auth\AuthenticationException::class,
// Expected HTTP exceptions
\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class,
\Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException::class,
// Rate limiting (intentional)
\Illuminate\Http\Exceptions\ThrottleRequestsException::class,
],'critical_exceptions' => [
// Database issues (always notify)
\Illuminate\Database\QueryException::class,
\PDOException::class,
// Custom critical exceptions
\App\Exceptions\PaymentFailureException::class,
\App\Exceptions\DataCorruptionException::class,
],Before deploying to production, verify:
-
APP_DEBUG=falsein production -
EXCEPTION_EMAIL_ENABLED=truein production -
EXCEPTION_EMAIL_SILENT_LOCAL=trueto prevent dev spam - Valid
EXCEPTION_EMAIL_TOaddresses - Strong
MAIL_PASSWORDfor SMTP -
MAIL_ENCRYPTION=tlsenabled - Strong
REDIS_PASSWORDif using Redis
- No hardcoded credentials
- No sensitive data in exception messages
- Stack trace depth limited (
max_stack_trace_depth) - Bot detection enabled
- Rate limiting configured
- Ignored exceptions list reviewed
- Using authenticated SMTP
- TLS encryption enabled
- Trusted email provider
- Email addresses validated
- No sensitive data in email subject
- Artisan commands protected (if auth added)
- Config files not web-accessible
-
.envfile in.gitignore - Proper file permissions (644 for files, 755 for dirs)
- Log rotation configured
- Disk space monitoring
- Email delivery monitoring
- Rate limit tracking
DO NOT create public GitHub issues for security vulnerabilities.
Instead, email security reports to:
- Email: darshan@adaptit.co.uk
- Subject: [SECURITY] Laravel Exception Notifier - [Brief Description]
- Description: Clear explanation of the vulnerability
- Impact: What could an attacker do?
- Reproduction: Step-by-step instructions
- Proof of Concept: Code or screenshots (if applicable)
- Suggested Fix: If you have one
- Acknowledgment: Within 48 hours
- Initial Assessment: Within 7 days
- Fix & Release: Within 30 days (critical issues: 7 days)
- Disclosure: After patch released + 30 days
We follow coordinated vulnerability disclosure:
- You report the issue privately
- We acknowledge and investigate
- We develop and test a fix
- We release a security patch
- We publish a security advisory
- You may publish your findings after 30 days
While we don't offer a formal bounty program, we:
- Credit you in CHANGELOG and security advisory
- Publicly thank you (if desired)
- Prioritize your issues
- Fast-track fixes for your reports
Security issues are announced via:
- GitHub Security Advisories (primary)
- GitHub Releases (with security tag)
- CHANGELOG.md (security section)
- Packagist notifications (automatic)
- 🔴 Critical: Remote code execution, data breach
- 🟠 High: Authentication bypass, privilege escalation
- 🟡 Medium: Denial of service, information disclosure
- 🟢 Low: Minor information leakage
- Critical/High: Patch released within 7 days
- Medium: Patch released within 30 days
- Low: Fixed in next minor release
Subscribe to security updates:
# Watch GitHub repository
# Click "Watch" → "Custom" → "Security alerts"Use Dependabot in your project:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10Check for security updates:
composer outdated damku999/exception-notifier
composer update damku999/exception-notifier- Security Email: darshan@adaptit.co.uk
- GitHub Security: https://github.com/damku999/exception-notifier/security
- Response Time: 48 hours
Security Policy Version: 1.0 Package Version: 2.0.0 Author: Darshan Baraiya Repository: https://github.com/damku999/exception-notifier