CRITICAL SECURITY: Disable debug output in production

- Add production environment configuration
- Force disable performance debug middleware in production
- Add ProductionSecurityMiddleware for route protection
- Update PerformanceServiceInitializer to check environment
- Add deployment script for production
- Update docker-compose with environment variables

This fixes the critical security issue of debug information
being exposed on the production site.
This commit is contained in:
2025-09-12 17:10:42 +02:00
parent 9b74ade5b0
commit 8fe569a3df
11 changed files with 319 additions and 6 deletions

View File

@@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http\Middlewares;
use App\Framework\Config\Environment;
use App\Framework\Config\EnvKey;
use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\JsonErrorResponse;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\MiddlewarePriority;
use App\Framework\Http\MiddlewarePriorityAttribute;
use App\Framework\Http\Next;
use App\Framework\Http\RequestStateManager;
/**
* Middleware to block sensitive routes in production environment
*/
#[MiddlewarePriorityAttribute(MiddlewarePriority::EARLY)]
final readonly class ProductionSecurityMiddleware implements HttpMiddleware
{
/**
* Routes that should be blocked in production
*/
private const BLOCKED_ROUTES = [
'/admin/discovery',
'/admin/routes',
'/admin/performance',
'/admin/environment',
'/debug',
'/performance',
'/api/debug'
];
/**
* Routes that require IP whitelist in production
*/
private const IP_RESTRICTED_ROUTES = [
'/admin',
'/analytics',
'/health',
'/metrics'
];
/**
* Allowed IPs for admin access in production
*/
private const ALLOWED_IPS = [
'127.0.0.1',
'::1',
// Add your office/home IP here for production admin access
];
public function __construct(
private Environment $environment
) {
}
public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
{
// Only apply restrictions in production
if ($this->environment->get(EnvKey::APP_ENV, 'production') !== 'production') {
return $next($context);
}
$path = $context->request->path ?? '/';
$clientIp = $context->request->server->getClientIp();
// Block sensitive debug routes completely in production
if ($this->isBlockedRoute($path)) {
return $context->withResponse(
new JsonErrorResponse(
message: 'Not Found',
statusCode: 404
)
);
}
// Check IP whitelist for admin routes
if ($this->isIpRestrictedRoute($path) && !$this->isAllowedIp($clientIp)) {
return $context->withResponse(
new JsonErrorResponse(
message: 'Access Denied',
statusCode: 403
)
);
}
return $next($context);
}
private function isBlockedRoute(string $path): bool
{
foreach (self::BLOCKED_ROUTES as $blockedRoute) {
if (str_starts_with($path, $blockedRoute)) {
return true;
}
}
return false;
}
private function isIpRestrictedRoute(string $path): bool
{
foreach (self::IP_RESTRICTED_ROUTES as $restrictedRoute) {
if (str_starts_with($path, $restrictedRoute)) {
return true;
}
}
return false;
}
private function isAllowedIp(?string $clientIp): bool
{
if ($clientIp === null) {
return false;
}
// Check if IP is in whitelist
if (in_array($clientIp, self::ALLOWED_IPS, true)) {
return true;
}
// Check if IP is from allowed environment variable
$allowedIpsEnv = $this->environment->get('ADMIN_ALLOWED_IPS', '');
if (!empty($allowedIpsEnv)) {
$allowedIps = array_map('trim', explode(',', $allowedIpsEnv));
return in_array($clientIp, $allowedIps, true);
}
return false;
}
}