Horizon Security Analyzer
| Analyzer ID | Category | Severity | Time To Fix |
|---|---|---|---|
horizon-security | 🛡️ Security | High | 10 minutes |
What This Checks
This analyzer validates the security configuration of Laravel Horizon's dashboard to ensure it is not publicly accessible. Horizon provides a web-based dashboard for monitoring Redis queues, failed jobs, and job metrics -- sensitive operational data that must be protected.
Checks Performed:
Service Provider Validation
- HorizonServiceProvider existence - Verifies
app/Providers/HorizonServiceProvider.phpexists - Horizon::auth() gate - Checks that an authorization gate is defined to restrict dashboard access
- Hardcoded boolean returns - Flags
return true;in the auth gate as insecure (grants access to everyone) - Auth-only checks - Flags gates that only verify the user is logged in (
auth()->check()) without restricting to specific users or roles - Auth middleware - Checks for authentication middleware on the Horizon routes
Configuration File Validation
- Middleware configuration - Flags when
config/horizon.phponly includeswebmiddleware withoutauth, but only when noviewHorizongate is configured. When a gate is present it already handles authorization, makingauthmiddleware redundant.
Redis Configuration Validation
- Redis password - Flags when
config/database.phphas a hardcodednullor empty password for the Redis connection instead of usingenv('REDIS_PASSWORD')
Why It Matters
An unsecured Horizon dashboard exposes critical operational information that can be leveraged by attackers:
- Sensitive Job Data - Job payloads may contain user emails, payment data, API tokens, and other PII
- Application Architecture - Queue names, job classes, and processing patterns reveal internal architecture
- Failed Job Information - Failed job details often include stack traces, database queries, and configuration values
- Queue Metrics - Throughput and timing data can inform denial-of-service attack strategies
- Redis Configuration - Connection details and memory usage patterns can be exploited
A publicly accessible Horizon dashboard is equivalent to giving an attacker a monitoring console for your application's background processing.
How to Fix
Quick Fix (5 minutes)
Add a viewHorizon gate in your HorizonServiceProvider. A role-check or email-allowlist is sufficient — no environment-specific branching is required.
Before (❌):
// app/Providers/HorizonServiceProvider.php
class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
protected function gate(): void
{
// NO AUTHORIZATION - Horizon is publicly accessible!
}
}After (✅):
// app/Providers/HorizonServiceProvider.php
use Illuminate\Support\Facades\Gate;
class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
protected function gate(): void
{
Gate::define('viewHorizon', function ($user): bool {
return $user->hasRole('admin');
});
}
}Alternatively, drive the allowlist from an environment variable so no code change is needed per environment:
protected function gate(): void
{
Gate::define('viewHorizon', function ($user): bool {
$authorized = array_filter(
array_map('trim', explode(',', config('horizon.authorized_emails', '')))
);
return in_array($user->email, $authorized, strict: true);
});
}# .env
HORIZON_AUTHORIZED_EMAILS=admin@yourcompany.com,devops@yourcompany.comProper Fix (10 minutes)
Also make sure weak gates are not in use:
Before (❌) — grants access to everyone:
protected function gate(): void
{
Horizon::auth(function ($request) {
return true; // VULNERABLE: no real authorization
});
}Before (❌) — authentication only, not authorization:
protected function gate(): void
{
Horizon::auth(function ($request) {
return auth()->check(); // Any logged-in user can access Horizon
});
}After (✅) — proper role/permission check:
// app/Providers/HorizonServiceProvider.php
use Illuminate\Support\Facades\Gate;
class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
protected function gate(): void
{
Gate::define('viewHorizon', function ($user): bool {
return $user->hasRole('admin')
|| $user->can('view-horizon');
});
}
}Optional: add auth middleware as an extra layer (not required when a gate is configured):
// config/horizon.php
return [
'middleware' => ['web', 'auth'], // 'auth' ensures unauthenticated users get a login redirect
// ...
];Fix Redis Authentication
Ensure the Redis password is read from environment variables, not hardcoded:
Before (❌):
// config/database.php
'redis' => [
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => null, // Unauthenticated Redis
'port' => env('REDIS_PORT', 6379),
],
],After (✅):
// config/database.php
'redis' => [
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
],
],# .env
REDIS_PASSWORD=your-strong-redis-passwordReferences
- Laravel Horizon Documentation
- Laravel Horizon Authorization
- OWASP Access Control Cheat Sheet
- CWE-284: Improper Access Control
- CWE-306: Missing Authentication for Critical Function
Related Analyzers
- Nova Security Analyzer - Validates Laravel Nova admin panel security
- Authentication Authorization Analyzer - Checks authentication and authorization configuration
- Debug Mode Analyzer - Ensures debug mode is disabled in production
- Env HTTP Accessibility Analyzer - Prevents sensitive files from being publicly accessible