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

@@ -44,3 +44,28 @@ OPCACHE_ENABLED=true
REDIS_HOST=production-redis-host REDIS_HOST=production-redis-host
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_PASSWORD=SECURE_REDIS_PASSWORD_HERE 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

View File

@@ -60,6 +60,7 @@ services:
environment: environment:
PHP_IDE_CONFIG: "serverName=docker" PHP_IDE_CONFIG: "serverName=docker"
APP_ENV: ${APP_ENV:-development} APP_ENV: ${APP_ENV:-development}
APP_DEBUG: ${APP_DEBUG:-true}
healthcheck: healthcheck:
test: [ "CMD", "php", "-v" ] test: [ "CMD", "php", "-v" ]
interval: 30s interval: 30s

83
scripts/deploy-production.sh Executable file
View File

@@ -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"

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;
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace App\Framework\Logging\Formatter;
final class LineFormatter
{
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Framework\Logging\Formatter;
interface LogFormatter
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace App\Framework\Meta;
final class MetaTag
{
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Framework\Meta;
enum MetaTagType: string
{
case NAME = 'name';
case PROPERTY = 'property';
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Framework\Meta;
enum MetaTagProperty
{
}

View File

@@ -17,6 +17,8 @@ use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\Performance\EnhancedPerformanceCollector; use App\Framework\Performance\EnhancedPerformanceCollector;
use App\Framework\Performance\PerformanceConfig; use App\Framework\Performance\PerformanceConfig;
use App\Framework\Performance\PerformanceReporter; use App\Framework\Performance\PerformanceReporter;
use App\Framework\Config\Environment;
use App\Framework\Config\EnvKey;
#[MiddlewarePriorityAttribute(MiddlewarePriority::LAST)] #[MiddlewarePriorityAttribute(MiddlewarePriority::LAST)]
final readonly class PerformanceDebugMiddleware implements HttpMiddleware final readonly class PerformanceDebugMiddleware implements HttpMiddleware
@@ -24,7 +26,8 @@ final readonly class PerformanceDebugMiddleware implements HttpMiddleware
public function __construct( public function __construct(
private PerformanceCollectorInterface $collector, private PerformanceCollectorInterface $collector,
private PerformanceConfig $config, private PerformanceConfig $config,
private PerformanceReporter $reporter private PerformanceReporter $reporter,
private Environment $environment
) { ) {
} }
@@ -39,6 +42,16 @@ final readonly class PerformanceDebugMiddleware implements HttpMiddleware
private function handlePerformanceOutput(MiddlewareContext $context, RequestStateManager $stateManager): MiddlewareContext private function handlePerformanceOutput(MiddlewareContext $context, RequestStateManager $stateManager): MiddlewareContext
{ {
// EMERGENCY SECURITY DISABLE: Force disable debug output immediately
// Until environment loading is fixed, completely disable debug output
return $context;
// SECURITY: Never output debug info in production, regardless of config
$appEnv = $this->environment->get(EnvKey::APP_ENV, 'production');
if ($appEnv === 'production') {
return $context;
}
// Check if performance tracking is enabled // Check if performance tracking is enabled
if (! $this->config->enabled) { if (! $this->config->enabled) {
return $context; return $context;

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Framework\Performance; namespace App\Framework\Performance;
use App\Framework\Config\Environment;
use App\Framework\Config\EnvKey;
use App\Framework\DI\Container; use App\Framework\DI\Container;
use App\Framework\DI\Initializer; use App\Framework\DI\Initializer;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface; use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
@@ -13,7 +15,8 @@ use App\Framework\Performance\Contracts\PerformanceServiceInterface;
final readonly class PerformanceServiceInitializer final readonly class PerformanceServiceInitializer
{ {
public function __construct( 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) // Get the existing collector instance from container (registered in entry points)
$collector = $this->container->get(PerformanceCollectorInterface::class); $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( $config = new PerformanceConfig(
enabled: true, enabled: $performanceEnabled,
useEnhancedCollector: true, detailedReports: $performanceEnabled, // Session info only in dev
useEnhancedCollector: $performanceEnabled,
includeStackTrace: false, // Never include stack traces
thresholds: [ thresholds: [
'slow_query_ms' => 100, 'slow_query_ms' => 100,
'slow_request_ms' => 1000, 'slow_request_ms' => 1000,
'high_memory_mb' => 50, 'high_memory_mb' => 50,
] ],
excludedPaths: ['/health', '/metrics', '/api']
); );
$reporter = new PerformanceReporter($collector); $reporter = new PerformanceReporter($collector);