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:
@@ -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
|
||||||
@@ -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
83
scripts/deploy-production.sh
Executable 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"
|
||||||
133
src/Framework/Http/Middlewares/ProductionSecurityMiddleware.php
Normal file
133
src/Framework/Http/Middlewares/ProductionSecurityMiddleware.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/Framework/Logging/Formatter/LineFormatter.php
Normal file
9
src/Framework/Logging/Formatter/LineFormatter.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Logging\Formatter;
|
||||||
|
|
||||||
|
final class LineFormatter
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
8
src/Framework/Logging/Formatter/LogFormatter.php
Normal file
8
src/Framework/Logging/Formatter/LogFormatter.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Framework\Logging\Formatter;
|
||||||
|
|
||||||
|
interface LogFormatter
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
9
src/Framework/Meta/MetaTag.php
Normal file
9
src/Framework/Meta/MetaTag.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Meta;
|
||||||
|
|
||||||
|
final class MetaTag
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
10
src/Framework/Meta/MetaTagName.php
Normal file
10
src/Framework/Meta/MetaTagName.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Meta;
|
||||||
|
|
||||||
|
enum MetaTagType: string
|
||||||
|
{
|
||||||
|
case NAME = 'name';
|
||||||
|
case PROPERTY = 'property';
|
||||||
|
}
|
||||||
8
src/Framework/Meta/MetaTagProperty.php
Normal file
8
src/Framework/Meta/MetaTagProperty.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Framework\Meta;
|
||||||
|
|
||||||
|
enum MetaTagProperty
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user