Laravel Debug Routes in Production: How to Find and Remove Test Endpoints
Debug and test routes left in production expose phpinfo(), database dumps, and internal application state to anyone who finds them.
Debug and test routes are temporary endpoints developers create during development to inspect application state, test functionality, or quickly verify a fix. Common examples include routes that call phpinfo(), dump environment variables, display database contents, trigger test emails, or show session data. These routes solve immediate development needs and are typically created quickly in routes/web.php without thought about their lifecycle.
Attackers specifically target debug-style URL paths because they are found so frequently. Automated scanners maintain wordlists of thousands of common debug path patterns: /phpinfo, /info.php, /test, /debug, /_dev, /.env.bak, /api/debug. These scans run against every domain discovered on the internet, continuously. A phpinfo() page reveals your PHP version, all loaded extensions, environment variables (including those from .env), server configuration, compilation options, and full file paths — everything an attacker needs to tailor further exploits.
The mechanism by which debug routes survive to production is straightforward: they get committed to routes/web.php during development and are never removed. Deployment pipelines typically have no way to detect their presence. Feature branches with debug routes get merged to main. Even when a developer adds if (env('APP_ENV') === 'local') guards, the route may still register before the environment check. The only reliable prevention is automated testing that verifies debug-pattern routes do not exist in the registered route list.
The Problem
Developers often create temporary routes during development for testing — routes that dump phpinfo(), display database contents, test email sending, or expose internal state. If these routes reach production, they become an information disclosure vulnerability. Attackers routinely scan for common debug paths like /test, /debug, /phpinfo, /info, and /_dev. A single phpinfo() route reveals your PHP version, extensions, environment variables, and server configuration.
How to Fix
-
1
Find debug routes in your route files
Search for common debug patterns:
grep -rn 'phpinfo\|dd(\|dump(\|var_dump\|print_r' routes/ --include='*.php' grep -rn 'test\|debug\|tmp\|temp\|_dev' routes/ --include='*.php'Also list all registered routes and look for suspicious ones:
php artisan route:list | grep -i 'test\|debug\|info\|dump' -
2
Remove or protect debug routes
Delete debug routes entirely, or if they are needed for development, wrap them in an environment check:
// REMOVE completely — preferred // Route::get('/phpinfo', fn() => phpinfo());// Or restrict to local environment only if (app()->environment('local')) { Route::get('/debug-info', function () { return response()->json([ 'php' => phpversion(), 'laravel' => app()->version(), ]); }); } -
3
Add a CI check to prevent debug routes in production
Add a test that fails if debug routes exist:// tests/Feature/NoDebugRoutesTest.php public function test_no_debug_routes_exist(): void { $debugPatterns = ['phpinfo', 'debug', '_test', '_dev']; $routes = collect(Route::getRoutes())->map(fn ($r) => $r->uri());foreach ($debugPatterns as $pattern) { $matches = $routes->filter(fn ($uri) => str_contains($uri, $pattern)); $this->assertEmpty($matches, "Debug route found: {$matches->implode(', ')}"); } }
How to Verify
Check common debug paths return 404:
curl -s -o /dev/null -w '%{http_code}' https://yourapp.com/phpinfo
curl -s -o /dev/null -w '%{http_code}' https://yourapp.com/test
curl -s -o /dev/null -w '%{http_code}' https://yourapp.com/debug
All should return 404. Run php artisan stackshield:scan --check=SS051 to verify.
Prevention
Never create debug routes in main route files. Use php artisan tinker for ad-hoc debugging. Add the CI test above to catch debug routes before deployment. Use StackShield to scan for debug routes continuously.
Frequently Asked Questions
Is it safe to use dd() in controllers?
dd() (dump and die) should never exist in production code. It halts execution and outputs internal data to the browser. Use proper logging (Log::debug()) instead, and remove dd() calls before committing.
What about Laravel Telescope routes?
Telescope registers routes at /telescope by default. In production, either disable Telescope entirely (TELESCOPE_ENABLED=false) or restrict access using the gate in TelescopeServiceProvider. Stackshield check SS013 specifically monitors for this.
What specific information does a phpinfo() route expose?
A phpinfo() page exposes: PHP version and compiled-in extensions (useful for targeting known CVEs), all environment variables including those from .env (leaking APP_KEY, database credentials, API keys), the full path to every PHP file loaded (helping attackers navigate the filesystem), php.ini settings, and loaded PHP modules. This is equivalent to handing an attacker a blueprint of your server configuration.
How do debug routes survive the development-to-production deployment process?
They typically get committed to version-controlled route files during development and merged without code review. Even when a developer adds if (app()->environment("local")) guards, the route may still register in route caches before the environment check. The most reliable prevention is a test that explicitly checks that no route URIs match a blocklist of debug patterns, failing the CI build if they exist.
Should I use Laravel Pulse or Telescope instead of creating debug routes?
Yes. Laravel Pulse provides real-time application performance monitoring with proper authentication controls. Telescope provides request, query, and log inspection behind an authorization gate. Both are purpose-built for the production debugging use case and include access controls. Creating ad-hoc debug routes bypasses all safeguards and leaves sensitive data publicly accessible.
Related Security Terms
Related Guides
Laravel Debug Mode in Production: How to Disable APP_DEBUG and Stop Leaking Secrets
APP_DEBUG=true in production exposes stack traces, environment variables, and database credentials to anyone who triggers an error. Here is how to disable it safely and verify the fix.
How to Fix an Exposed Laravel Telescope Dashboard
Your Laravel Telescope dashboard is publicly accessible in production, exposing requests, queries, and application data. Secure it now.
How to Fix Exposed Laravel Ignition Error Pages
Laravel Ignition error pages are visible in production, leaking stack traces and environment details. Learn how to disable them.
Is your Laravel app exposed right now?
34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.