Files
cms-gov/app/Filters/SecurityHeaders.php

157 lines
7.2 KiB
PHP

<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class SecurityHeaders implements FilterInterface
{
/**
* Do whatever processing this filter needs to do.
* By default it should not return anything during
* normal execution. However, when an abnormal state
* is found, it should return an instance of
* CodeIgniter\HTTP\Response. If it does, script
* execution will end and that Response will be
* sent back to the client, allowing for error pages,
* redirects, etc.
*
* @param RequestInterface $request
* @param array|null $arguments
*
* @return mixed
*/
public function before(RequestInterface $request, $arguments = null)
{
// No action needed before request
}
/**
* Allows After filters to inspect and modify the response
* object as needed. This method does not allow any way
* to stop execution of other after filters, short of
* throwing an Exception or Error.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param array|null $arguments
*
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
$isProduction = ENVIRONMENT === 'production';
// ============================================================
// BASIC SECURITY HEADERS
// ============================================================
// X-Frame-Options: Mencegah clickjacking attacks
// SAMEORIGIN = hanya allow framing dari same origin
$response->setHeader('X-Frame-Options', 'SAMEORIGIN');
// X-Content-Type-Options: Mencegah MIME type sniffing
// nosniff = browser tidak boleh menebak content type
$response->setHeader('X-Content-Type-Options', 'nosniff');
// X-XSS-Protection: Legacy header untuk browser lama (optional)
// Mode=block = block page jika XSS terdeteksi
$response->setHeader('X-XSS-Protection', '1; mode=block');
// Referrer-Policy: Kontrol informasi referrer yang dikirim
// strict-origin-when-cross-origin = kirim full URL untuk same-origin,
// hanya origin untuk cross-origin HTTPS, tidak ada untuk HTTP
$response->setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Permissions-Policy: Kontrol fitur browser yang bisa digunakan
// Membatasi akses ke fitur seperti geolocation, camera, microphone, dll
// Hanya gunakan feature yang didukung oleh browser modern
$permissionsPolicy = [
'geolocation=()', // Geolocation API
'camera=()', // Camera access
'microphone=()', // Microphone access
'payment=()', // Payment Request API
'usb=()', // WebUSB API
'magnetometer=()', // Magnetometer sensor
'gyroscope=()', // Gyroscope sensor
'accelerometer=()', // Accelerometer sensor
'ambient-light-sensor=()', // Ambient light sensor
'autoplay=()', // Autoplay media
'fullscreen=()', // Fullscreen API
'picture-in-picture=()', // Picture-in-picture
];
$response->setHeader('Permissions-Policy', implode(', ', $permissionsPolicy));
// ============================================================
// HSTS (HTTP Strict Transport Security)
// ============================================================
// Hanya aktif di production dengan HTTPS
// max-age=31536000 = 1 tahun
// includeSubDomains = berlaku untuk semua subdomain
// preload = bisa ditambahkan ke HSTS preload list (optional)
if ($isProduction) {
$response->setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
// ============================================================
// CONTENT SECURITY POLICY (CSP)
// ============================================================
// CSP directives untuk mencegah XSS attacks
// Konfigurasi disesuaikan untuk TailAdmin dan Alpine.js
//
// CATATAN: Alpine.js memerlukan 'unsafe-eval' untuk mengevaluasi
// expression JavaScript (x-data, x-show, dll). Ini trade-off security
// yang diperlukan untuk Alpine.js bekerja dengan baik.
$cspDirectives = [
"default-src 'self'", // Default: hanya dari same origin
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net", // Script: allow inline dan eval untuk Alpine.js
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", // Style: allow inline untuk Tailwind
"font-src 'self' data: https://fonts.gstatic.com", // Font: allow data URI dan Google Fonts
"img-src 'self' data: https:", // Image: allow data URI dan HTTPS
"connect-src 'self'", // AJAX/Fetch: hanya same origin
"frame-ancestors 'self'", // Frame: hanya same origin
"base-uri 'self'", // Base URI: hanya same origin
"form-action 'self'", // Form action: hanya same origin
"object-src 'none'", // Object/embed: tidak ada
];
// Hanya tambahkan upgrade-insecure-requests di production
if ($isProduction) {
$cspDirectives[] = "upgrade-insecure-requests";
}
$cspValue = implode('; ', $cspDirectives);
if ($isProduction) {
// Enforce CSP di production
$response->setHeader('Content-Security-Policy', $cspValue);
} else {
// Report-Only di development untuk testing
$response->setHeader('Content-Security-Policy-Report-Only', $cspValue);
// Juga set regular CSP untuk security audit tools
$response->setHeader('Content-Security-Policy', $cspValue);
}
// ============================================================
// ADDITIONAL SECURITY HEADERS
// ============================================================
// Cross-Origin-Embedder-Policy: Mencegah embedding dari cross-origin
// require-corp = require Cross-Origin Resource Policy
// $response->setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); // Optional, bisa break beberapa fitur
// Cross-Origin-Opener-Policy: Isolasi browsing context
// same-origin = hanya same-origin yang bisa access window
// $response->setHeader('Cross-Origin-Opener-Policy', 'same-origin'); // Optional
// Cross-Origin-Resource-Policy: Kontrol resource sharing
// same-origin = hanya same-origin yang bisa load resource
// $response->setHeader('Cross-Origin-Resource-Policy', 'same-origin'); // Optional
return $response;
}
}