diff --git a/app/Config/Filters.php b/app/Config/Filters.php index 0e2f6f2..d4e08b2 100644 --- a/app/Config/Filters.php +++ b/app/Config/Filters.php @@ -58,8 +58,9 @@ class Filters extends BaseFilters 'pagecache', // Web Page Caching ], 'after' => [ - 'pagecache', // Web Page Caching - 'performance', // Performance Metrics + 'pagecache', // Web Page Caching + 'performance', // Performance Metrics + 'secureheaders', // Unconditional security headers on all responses (including 404) ], ]; diff --git a/app/Filters/SecureHeadersFilter.php b/app/Filters/SecureHeadersFilter.php index 4244aba..df587f6 100644 --- a/app/Filters/SecureHeadersFilter.php +++ b/app/Filters/SecureHeadersFilter.php @@ -44,8 +44,21 @@ class SecureHeadersFilter implements FilterInterface // Cross-Origin isolation protections 'Cross-Origin-Opener-Policy' => 'same-origin-allow-popups', - // Content Security Policy (allowing self, inline scripts, styles, data URIs for canvas/charts) - 'Content-Security-Policy' => "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: https:; frame-ancestors 'self';", + // Content Security Policy (strict sources, trusted CDNs, preventing wildcards and injection) + 'Content-Security-Policy' => "default-src 'self'; " . + "script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: https://cdn.jsdelivr.net https://code.jquery.com https://cdn.datatables.net https://oss.maxcdn.com https://cdnjs.cloudflare.com; " . + "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdn.datatables.net https://fonts.googleapis.com https://cdnjs.cloudflare.com; " . + "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com; " . + "img-src 'self' data: blob: https://api.qrserver.com; " . + "connect-src 'self' blob: data:; " . + "frame-src 'self' data: blob:; " . + "media-src 'self' data: blob:; " . + "object-src 'none'; " . + "worker-src 'self' blob:; " . + "manifest-src 'self'; " . + "form-action 'self'; " . + "base-uri 'self'; " . + "frame-ancestors 'self';", ]; public function before(RequestInterface $request, $arguments = null) diff --git a/app/Views/auth/login.php b/app/Views/auth/login.php index 4aa8691..1387438 100644 --- a/app/Views/auth/login.php +++ b/app/Views/auth/login.php @@ -18,8 +18,6 @@ - - @@ -169,9 +167,15 @@ $ipLabel) { - if (stripos($ipLabel, 'Wi-Fi') !== false || stripos($ipLabel, 'Wireless') !== false) { - $defaultIp = $ipKey; - break; + // Default host: Always use the HTTP host requested by client (e.g. demo.e-atria.net), + // unless on localhost with detected LAN Wi-Fi + if (!$isLocalDev || empty($detectedNetworkInterfaces)) { + $defaultHost = $httpHost; + } else { + $defaultIp = ''; + foreach ($detectedNetworkInterfaces as $ipKey => $ipLabel) { + if (stripos($ipLabel, 'Wi-Fi') !== false || stripos($ipLabel, 'Wireless') !== false) { + $defaultIp = $ipKey; + break; + } } + if (!$defaultIp) { + $defaultIp = array_key_first($detectedNetworkInterfaces); + } + $defaultHost = $defaultIp . ':' . $serverPort; } - if (!$defaultIp && !empty($detectedNetworkInterfaces)) { - $defaultIp = array_key_first($detectedNetworkInterfaces); - } - if (!$defaultIp) { - $defaultIp = explode(':', $httpHost)[0]; - } - $defaultHost = ($defaultIp === 'localhost' || $defaultIp === '127.0.0.1') ? $httpHost : ($defaultIp . ':' . $serverPort); ?> @@ -276,21 +278,21 @@
diff --git a/public/.htaccess b/public/.htaccess index abac3cb..e87fed0 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -47,3 +47,21 @@ Options -Indexes # Disable server signature start ServerSignature Off # Disable server signature end + +# Security headers + + # Enforce HSTS globally on all responses (including 404s, redirects, and static assets) + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + Header always set X-Content-Type-Options "nosniff" + Header always set X-Frame-Options "SAMEORIGIN" + Header always set Referrer-Policy "strict-origin-when-cross-origin" + + + Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: https://cdn.jsdelivr.net https://code.jquery.com https://cdn.datatables.net https://oss.maxcdn.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdn.datatables.net https://fonts.googleapis.com https://cdnjs.cloudflare.com; font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com; img-src 'self' data: blob: https://api.qrserver.com; connect-src 'self' blob: data:; frame-src 'self' data: blob:; media-src 'self' data: blob:; object-src 'none'; worker-src 'self' blob:; manifest-src 'self'; form-action 'self'; base-uri 'self'; frame-ancestors 'self';" + Header set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" + + + Header set Cache-Control "public, max-age=86400, must-revalidate" + + + diff --git a/public/App/Doctor/index.html b/public/App/Doctor/index.html index 3a1880c..552440c 100644 --- a/public/App/Doctor/index.html +++ b/public/App/Doctor/index.html @@ -11,21 +11,20 @@ - - - - - - - - - - - + - - - + + + + + + + + + + + +