From 8fe569a3dfa7e1d51c1e5483bce27cb0cf79551c Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Fri, 12 Sep 2025 17:10:42 +0200 Subject: [PATCH] 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. --- .env.production | 27 +++- docker-compose.yml | 1 + scripts/deploy-production.sh | 83 +++++++++++ .../ProductionSecurityMiddleware.php | 133 ++++++++++++++++++ .../Logging/Formatter/LineFormatter.php | 9 ++ .../Logging/Formatter/LogFormatter.php | 8 ++ src/Framework/Meta/MetaTag.php | 9 ++ src/Framework/Meta/MetaTagName.php | 10 ++ src/Framework/Meta/MetaTagProperty.php | 8 ++ .../Middleware/PerformanceDebugMiddleware.php | 15 +- .../PerformanceServiceInitializer.php | 22 ++- 11 files changed, 319 insertions(+), 6 deletions(-) create mode 100755 scripts/deploy-production.sh create mode 100644 src/Framework/Http/Middlewares/ProductionSecurityMiddleware.php create mode 100644 src/Framework/Logging/Formatter/LineFormatter.php create mode 100644 src/Framework/Logging/Formatter/LogFormatter.php create mode 100644 src/Framework/Meta/MetaTag.php create mode 100644 src/Framework/Meta/MetaTagName.php create mode 100644 src/Framework/Meta/MetaTagProperty.php diff --git a/.env.production b/.env.production index 2c701c66..b9a601bf 100644 --- a/.env.production +++ b/.env.production @@ -43,4 +43,29 @@ GID=1000 OPCACHE_ENABLED=true REDIS_HOST=production-redis-host REDIS_PORT=6379 -REDIS_PASSWORD=SECURE_REDIS_PASSWORD_HERE \ No newline at end of file +REDIS_PASSWORD=SECURE_REDIS_PASSWORD_HERE + +# Analytics Configuration (Production) +ANALYTICS_ENABLED=true +ANALYTICS_TRACK_PAGE_VIEWS=true +ANALYTICS_TRACK_API_CALLS=true +ANALYTICS_TRACK_USER_ACTIONS=true +ANALYTICS_TRACK_ERRORS=true +ANALYTICS_TRACK_PERFORMANCE=false # Disable debug performance tracking + +# Session Fingerprinting (Production - Stricter) +SESSION_FINGERPRINT_STRICT=true +SESSION_FINGERPRINT_USER_AGENT=true +SESSION_FINGERPRINT_ACCEPT_LANGUAGE=true +SESSION_FINGERPRINT_IP_PREFIX=true +SESSION_FINGERPRINT_THRESHOLD=0.8 + +# JavaScript Logger Configuration +VITE_LOG_LEVEL=error + +# CRITICAL: Disable Xdebug in production +XDEBUG_MODE=off + +# Admin IP Whitelist (comma-separated) +# Add your office/home IP for production admin access +ADMIN_ALLOWED_IPS=127.0.0.1,::1 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index afcb1782..de2c1142 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,6 +60,7 @@ services: environment: PHP_IDE_CONFIG: "serverName=docker" APP_ENV: ${APP_ENV:-development} + APP_DEBUG: ${APP_DEBUG:-true} healthcheck: test: [ "CMD", "php", "-v" ] interval: 30s diff --git a/scripts/deploy-production.sh b/scripts/deploy-production.sh new file mode 100755 index 00000000..5a4fca09 --- /dev/null +++ b/scripts/deploy-production.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# Production Deployment Script +# This script prepares the application for production deployment + +set -e + +echo "๐Ÿš€ Starting Production Deployment..." + +# Check if we're in the right directory +if [ ! -f "composer.json" ]; then + echo "โŒ Error: Must be run from project root directory" + exit 1 +fi + +# Backup current .env if it exists +if [ -f ".env" ]; then + echo "๐Ÿ“ฆ Backing up current .env to .env.backup" + cp .env .env.backup +fi + +# Copy production environment file +echo "๐Ÿ“ Setting up production environment..." +cp .env.production .env + +# Clear all caches +echo "๐Ÿงน Clearing caches..." +rm -rf storage/cache/* +rm -rf var/cache/* +rm -rf cache/* + +# Install production dependencies (no dev dependencies) +echo "๐Ÿ“ฆ Installing production dependencies..." +composer install --no-dev --optimize-autoloader --no-interaction + +# Build production assets +echo "๐ŸŽจ Building production assets..." +npm run build + +# Set correct permissions +echo "๐Ÿ” Setting correct permissions..." +chmod -R 755 storage/ +chmod -R 755 var/ +chmod -R 755 public/ + +# Create necessary directories +mkdir -p storage/logs +mkdir -p storage/cache +mkdir -p var/cache +mkdir -p var/logs + +# Run database migrations +echo "๐Ÿ—„๏ธ Running database migrations..." +php console.php db:migrate --force + +# Clear PHP opcache if available +if command -v cachetool &> /dev/null; then + echo "๐Ÿ”„ Clearing PHP opcache..." + cachetool opcache:reset +fi + +# Restart services (if using systemctl) +if command -v systemctl &> /dev/null; then + echo "๐Ÿ”„ Restarting services..." + sudo systemctl restart php8.4-fpm + sudo systemctl restart nginx +fi + +echo "โœ… Production deployment complete!" +echo "" +echo "โš ๏ธ IMPORTANT REMINDERS:" +echo "1. Ensure APP_ENV=production in .env" +echo "2. Ensure APP_DEBUG=false in .env" +echo "3. Update database credentials if needed" +echo "4. Update ADMIN_ALLOWED_IPS in .env for admin access" +echo "5. Test the site to ensure everything works" +echo "" +echo "๐Ÿ”’ Security Checklist:" +echo "[ ] Performance debug is disabled" +echo "[ ] Session debug info is hidden" +echo "[ ] Admin routes are IP-restricted" +echo "[ ] Error messages are generic" +echo "[ ] HTTPS is enforced" \ No newline at end of file diff --git a/src/Framework/Http/Middlewares/ProductionSecurityMiddleware.php b/src/Framework/Http/Middlewares/ProductionSecurityMiddleware.php new file mode 100644 index 00000000..d37f6c9b --- /dev/null +++ b/src/Framework/Http/Middlewares/ProductionSecurityMiddleware.php @@ -0,0 +1,133 @@ +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; + } +} \ No newline at end of file diff --git a/src/Framework/Logging/Formatter/LineFormatter.php b/src/Framework/Logging/Formatter/LineFormatter.php new file mode 100644 index 00000000..d49d48ed --- /dev/null +++ b/src/Framework/Logging/Formatter/LineFormatter.php @@ -0,0 +1,9 @@ +environment->get(EnvKey::APP_ENV, 'production'); + if ($appEnv === 'production') { + return $context; + } + // Check if performance tracking is enabled if (! $this->config->enabled) { return $context; diff --git a/src/Framework/Performance/PerformanceServiceInitializer.php b/src/Framework/Performance/PerformanceServiceInitializer.php index 7642fdca..903c9ea8 100644 --- a/src/Framework/Performance/PerformanceServiceInitializer.php +++ b/src/Framework/Performance/PerformanceServiceInitializer.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\Framework\Performance; +use App\Framework\Config\Environment; +use App\Framework\Config\EnvKey; use App\Framework\DI\Container; use App\Framework\DI\Initializer; use App\Framework\Performance\Contracts\PerformanceCollectorInterface; @@ -13,7 +15,8 @@ use App\Framework\Performance\Contracts\PerformanceServiceInterface; final readonly class PerformanceServiceInitializer { public function __construct( - private Container $container + private Container $container, + private Environment $environment ) { } @@ -23,14 +26,25 @@ final readonly class PerformanceServiceInitializer // Get the existing collector instance from container (registered in entry points) $collector = $this->container->get(PerformanceCollectorInterface::class); + // Performance debugging should NEVER be enabled in production + $appEnv = $this->environment->get(EnvKey::APP_ENV, 'production'); + $isDebugEnabled = $this->environment->getBool(EnvKey::APP_DEBUG, false); + + // Strict check: Only enable in development AND debug mode + // Force disabled in production regardless of debug setting + $performanceEnabled = ($appEnv === 'development') && $isDebugEnabled; + $config = new PerformanceConfig( - enabled: true, - useEnhancedCollector: true, + enabled: $performanceEnabled, + detailedReports: $performanceEnabled, // Session info only in dev + useEnhancedCollector: $performanceEnabled, + includeStackTrace: false, // Never include stack traces thresholds: [ 'slow_query_ms' => 100, 'slow_request_ms' => 1000, 'high_memory_mb' => 50, - ] + ], + excludedPaths: ['/health', '/metrics', '/api'] ); $reporter = new PerformanceReporter($collector);