WP Ultimate SSL & Security

# Analisis Fitur & Keunggulan WP Ultimate SSL & Security v3.2.3

## 📋 FITUR-FITUR UTAMA

### 1. 🔒 SSL & HTTPS Management

| Fitur | Fungsi |
|——-|——–|
| `enable_ssl` | Mengaktifkan SSL dan mengubah siteurl/home ke HTTPS |
| `force_ssl` | Redirect 301 HTTP → HTTPS di level PHP |
| `force_ssl_htaccess` | Redirect via .htaccess (canonical host) |
| `mixed_content_fixer` | Memperbaiki mixed content via CSP `upgrade-insecure-requests` |
| Proxy-aware detection | Mendeteksi HTTPS di balik Cloudflare/proxy |

### 2. 🛡️ WordPress Hardening

“`php
// Fitur hardening yang tersedia:
– disable_xmlrpc // Nonaktifkan XML-RPC
– hide_version // Sembunyikan meta generator WordPress
– clean_head_meta // Hapus RSD & WLW manifest
– disable_file_edit // Set DISALLOW_FILE_EDIT
– disable_file_mods // Set DISALLOW_FILE_MODS
– disable_directory_browsing // Blokir directory listing
– block_sensitive_files // Lindungi file sensitif
– block_bad_queries_htaccess // Filter bad queries
– secure_uploads_htaccess // Proteksi direktori uploads
– disable_app_passwords // Nonaktifkan Application Passwords
“`

### 3. ⚔️ Brute Force Protection

“`php
// Arsitektur berbasis Object Cache (Zero-DB)
– bf_max_attempts: 10 // Maksimal percobaan login
– bf_lockout_duration: 60 // Durasi lockout (detik)
– bf_whitelist: “” // IP whitelist
– test_mode: read-only // Mode testing tanpa block

// Security Fix [3.2.2]:
// Login sukses TIDAK menghapus global IP counter
// → Mencegah attacker reset counter dengan 1 akun valid
“`

### 4. 🚦 Rate Limiting

“`php
// Konfigurasi:
– rate_limit_max: 180 // Maksimal request per window
– rate_limit_seconds: 60 // Window waktu (detik)

// Karakteristik:
– Mengembalikan HTTP 429 + header Retry-After
– Skip file statis (CSS, JS, gambar, font)
– Hanya untuk Public IP (skip private/reserved)
– Whitelist support
“`

### 5. 📜 Security Headers & CSP

**Headers Default:**
“`
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
Strict-Transport-Security: max-age=31536000 [; includeSubDomains] [; preload]
“`

**CSP Modes:**

| Mode | Karakteristik |
|——|—————|
| `strict` | Ketat, hanya `’self’` + `’unsafe-inline’` minimal |
| `compatible` | Lebih longgar, mengizinkan `https:`, `blob:`, `data:` |
| `report_only` | Hanya melaporkan pelanggaran, tidak memblokir |

**Custom CSP Exceptions:**
“`
script-src https://cdn.example.com
img-src data: https://images.example.com
style-src ‘unsafe-inline’ https://fonts.googleapis.com
“`

### 6. 🥷 Login Stealth Mode

“`php
// Cara kerja:
1. Akses langsung /wp-login.php → 404 Not Found
2. Admin yang mengakses /wp-admin/ mendapat signed cookie (wuss_admin_flow)
3. Cookie berlaku 5 menit, signed dengan HMAC-SHA256
4. Dengan cookie valid, bisa login normal

// Bypass tersedia untuk:
– logout, lostpassword, resetpass
– register (jika users_can_register aktif)
– OAuth/Social Login (OPT-IN via filter)
“`

### 7. 👤 User Enumeration Protection

“`php
// Dua level proteksi:
block_user_enum:
– Blokir ?author=N → redirect ke home

block_user_enum_strict:
– Blokir ?author=N
– Hapus /wp/v2/users dari REST API
– Author archives → 404 (dengan theme template fallback)
– Flush rewrite rules saat toggle ON/OFF
“`

### 8. 🔌 REST API Security

“`php
// Fitur:
– disable_rest_api: Batasi untuk guest
– limit_rest_per_page: Maks 50 item per request
– Whitelist endpoint: posts, pages, categories, tags, media, dll
– Guard mencegah overblocking pada REST path anomali
“`

### 9. ⚡ Performance Optimizations

“`php
– optimize_heartbeat: Kontrol interval Heartbeat API
→ Frontend: 60 detik
→ Backend: 30 detik (editor: 15 detik)

– remove_query_strings: Hapus ?ver= dari CSS/JS
→ Berguna untuk caching CDN

– block_public_feeds: Nonaktifkan RSS/Atom feeds
“`

### 10. ⏱️ Session Management

“`php
– admin_session_duration: 14400 detik (4 jam)
– Custom filter: wuss_apply_session_timeout
– auth_cookie_expiration hook untuk admin
“`

## 🏆 KEUNGGULAN ARSITEKTUR

### 1. Zero-DB Architecture

“`
Konvensional: Request → Database Query → Response
WUSS: Request → Object Cache → Response
“`

**Keuntungan:**
– Brute force counter & rate limit TIDAK menyentuh database
– Mengurangi beban MySQL di bawah serangan
– Skalabilitas linier dengan Redis/Memcached

### 2. Redis-Optimized Counter

“`php
private function cache_incr_with_ttl($key, $ttl)
{
// Atomic increment dengan TTL
wp_cache_add($key, 0, WUSS_CACHE_GROUP, $ttl);
$count = wp_cache_incr($key, 1, WUSS_CACHE_GROUP);

if ($count === false) {
// [3.2.2] Fallback aman: JANGAN reset ke 1
$existing = wp_cache_get($key, WUSS_CACHE_GROUP);
if ($existing === false) {
$this->cache_set($key, 1, $ttl);
return 1;
}
$next = max(1, (int) $existing + 1);
$this->cache_set($key, $next, $ttl);
return $next;
}
return (int) $count;
}
“`

**Fix Kritis [3.2.2]:** Fallback tidak lagi reset counter ke 1 jika `wp_cache_incr()` gagal → Mencegah attacker bypass lockout dengan memicu race condition.

### 3. OPcache-Safe Design

“`
❌ Pattern berbahaya:
wp_cache_flush_group() → Invalidasi seluruh cache group
→ Memaksa recompile PHP files di OPcache

✅ Pattern WUSS:
Cache key dengan fingerprint version
→ Hanya cache spesifik yang di-invalidate
→ OPcache tetap warm
“`

“`php
private function get_csp_cache_key($context)
{
$fingerprint = [
‘version’ => WUSS_VERSION,
‘cache_ver’ => get_option(“wuss_csp_cache_version”, “1”),
‘builder’ => ‘csp-parser-v4’,
‘context’ => $context,
‘ssl_active’ => $ssl_active ? 1 : 0,
‘mixed’ => !empty($this->options[“mixed_content_fixer”]) ? 1 : 0,
‘mode’ => $this->options[“csp_mode”] ?? ‘strict’,
‘exceptions’ => $this->options[“csp_custom_exceptions”] ?? ”,
‘report_uri’ => $report_uri,
];
return $this->generate_cache_key(“csp”, wp_json_encode($fingerprint));
}
“`

### 4. High-Traffic Micro-Optimizations (v3.2.3)

“`php
// [PERF] Request-local cache untuk hasil HTTPS trust
private $is_forwarded_https_cache = null;

// [PERF] Lazy cache untuk daftar trusted proxy
private $trusted_proxies_list = null;

// [PERF] Cached headers_list() per request
private $sent_headers_cache = null;

// Dampak pada high-traffic:
// – Menghilangkan redundansi filter_var() di hot-path
// – apply_filters() hanya dieksekusi 1x per request
// – Mengurangi 1x filter_var per hop pada XFF parsing
“`

### 5. Proxy-Aware IP Detection

“`php
// X-Forwarded-For parsing yang aman:
// 1. Baca kanan-ke-kiri (hop terakhir = paling dekat dengan origin)
// 2. Skip trusted proxy hops
// 3. Validasi Public IP untuk Cloudflare header

// Trusted proxies termasuk:
// – 127.0.0.1, ::1 (localhost)
// – Full Cloudflare IPv4 & IPv6 ranges
// – Extendable via filter: wuss_trusted_proxy_ips
“`

### 6. CSP Parser v4 – Production Hardened

“`
Fitur keamanan parser:
├── Directive-aware semicolon splitting
├── Tolak karakter kontrol (0x00-0x1F, 0x7F)
├── Tolak ‘;’ di level token (injection prevention)
├── Raw ‘<>’ character support (bukan HTML entities)
├── Whitelist directive names
├── Preservative sanitizer (tidak modify valid CSP)
└── Batasan 8KB untuk raw directives
“`

### 7. State Management & Rollback

“`php
// [3.2.2] Admin save melakukan rollback jika .htaccess gagal ditulis
// → Mencegah inconsistent state antara option DB dan file system

// Backup .htaccess sekali ke .htaccess.wuss-backup
// → Recovery path jika terjadi masalah
“`

## 🔧 EXTENSIBILITY VIA FILTERS

“`php
// Proxy Configuration
apply_filters(‘wuss_trusted_proxy_ips’, […]);
apply_filters(‘wuss_trust_cloudflare_header’, true, $remote_ip, $cf_ip);

// CSP Configuration
apply_filters(‘wuss_security_headers’, […]);
apply_filters(‘wuss_send_csp_header’, true, $csp, $context);
apply_filters(‘wuss_csp_report_uri’, ”);
apply_filters(‘wuss_allowed_csp_custom_directives’, […]);

// Login Stealth
apply_filters(‘wuss_login_stealth_allow_oauth_callback_bypass’, false);
apply_filters(‘wuss_login_stealth_allowed_params’, […]);
apply_filters(‘wuss_login_stealth_allowed_oauth_providers’, […]);

// REST API
apply_filters(‘wuss_allowed_rest_endpoints’, […]);

// Session
apply_filters(‘wuss_apply_session_timeout’, $duration);
“`

## 📊 PERBANDINGAN DENGAN PLUGIN KONVENSIONAL

| Aspek | Plugin Konvensional | WUSS |
|——-|———————|——|
| Brute Force Counter | Database table | Object Cache (Redis) |
| Rate Limiting | Custom table / .htaccess | Object Cache |
| CSP Generation | Setiap request | Cached 6 jam |
| HTTPS Detection | `$_SERVER[‘HTTPS’]` saja | Proxy-aware multi-layer |
| Cache Invalidation | Flush all | Fingerprint-based |
| IP Detection | `REMOTE_ADDR` saja | XFF + CF-Connecting-IP + CIDR matching |
| Login Stealth | Redirect saja | Signed cookie + 404 |
| State Consistency | Best effort | Rollback on failure |

## ⚠️ CATATAN PENTING

1. **Require External Object Cache** untuk fitur Brute Force & Rate Limiting
– Jika tidak ada Redis/Memcached, fitur ini auto-disable

2. **Tidak cocok untuk Nginx** untuk fitur .htaccess
– Ada no-op guard untuk mencegah error di Nginx/read-only hosting

3. **Test Mode** tersedia untuk validasi sebelum production
– Logging tanpa actually blocking

4. **Minimum Requirements:**
– PHP 7.4+
– WordPress 5.5+

Plugin ini menunjukkan arsitektur yang **enterprise-grade** dengan fokus pada **performance di high-traffic**, **security hardening yang mendalam**, dan **state consistency** yang robust.

<?php
/*
Plugin Name: WP Ultimate SSL & Security
Plugin URI: https://irawankosasih.com
Description: Plugin All-in-one untuk SSL, WordPress Hardening, manajemen aturan .htaccess, Brute Force Protection. Arsitektur Zero-DB, OPcache-Safe, dan Redis-Optimized.
Version: 3.2.3
Author: Asisten AI
Author URI: https://irawankosasih.com
License: GPL v2 or later
Text Domain: wp-ultimate-ssl-security
Requires PHP: 7.4
Requires at least: 5.5
*/

if (!defined(“ABSPATH”)) {
exit();
}

define(“WUSS_VERSION”, “3.2.3”);
define(“WUSS_MIN_PHP_VERSION”, “7.4”);
define(“WUSS_MIN_WP_VERSION”, “5.5”);
define(“WUSS_CACHE_GROUP”, “wuss”);

/**
* Class WP_Ultimate_SSL_Security
*
* Changelog 3.2.3 (High-Traffic Hot-Path Micro-Optimizations):
* – [PERF] is_forwarded_https_trusted(): Hasil evaluasi HTTPS & proxy trust di-cache ke properti class. Mencegah redundansi filter_var, pembacaan $_SERVER, dan strtolower di hot-path (CSP, SSL Force, Headers).
* – [PERF] is_trusted_proxy(): Daftar wuss_trusted_proxy_ips kini di-lazy-cache. WordPress Hook API (apply_filters) hanya dieksekusi 1x per request, menghilangkan overhead iterasi hook di dalam loop XFF.
* – [PERF] get_user_ip(): Refactor parsing X-Forwarded-For. Pengecekan Public IP dan validasi format digabung sedemikian rupa sehingga memangkas 1x pemanggilan filter_var per hop pada 99% request klien asli.
*
* Changelog 3.2.2 (Enterprise Logic/State Hardening):
* – [SEC][FIX] Brute Force: login sukses tidak lagi menghapus global IP failed-attempt counter.
* – [SEC][FIX] Proxy IP detection: X-Forwarded-For diproses kanan-ke-kiri dan skip trusted proxy hop.
* – [SEC][FIX] .htaccess Force SSL + Trust Proxy ditolak secara default untuk mencegah spoofed X-Forwarded-Proto bypass pada origin publik.
* – [SEC][FIX] cache_incr_with_ttl(): fallback tidak lagi reset counter existing ke 1 jika wp_cache_incr() gagal.
* – [STATE][FIX] Admin save melakukan rollback option terkait jika .htaccess/uploads rule gagal ditulis.
* – [CACHE][FIX] CSP cache invalidation memakai wuss_csp_cache_version dalam fingerprint.
* – [COMPAT][FIX] REST restriction guard agar tidak overblock jika REST path kosong/anomali.
* – [COMPAT][FIX] Flush rewrite rules saat block_user_enum_strict berubah ON maupun OFF.
* – [FIX] enable_ssl_urls() memakai wp_parse_url/preg_replace scheme-safe, bukan strpos/str_replace longgar.
* – [UX] Rename CSP mode Strict menjadi Strict Compatible agar ekspektasi security lebih akurat.
* – [HARDENING] Batasi Raw CSP Directives maksimal 8KB.
*
* Changelog 3.2.1 (Production Polish & High-Traffic Optimizations):
* – [SEC][FIX] Login Stealth: OAuth/Social login bypass sekarang default opt-in (false) via filter ‘wuss_login_stealth_allow_oauth_callback_bypass’.
* – [COMPAT][FIX] update_htaccess(): No-op jika tidak ada fitur .htaccess aktif. Mencegah write error di Nginx/read-only hosting saat hanya menyimpan setting CSP.
* – [SEC][FIX] check_ssl_force(): Hardening canonical host regex dan validasi $request_uri path.
* – [PERF] is_trusted_proxy(): Ditambahkan request-local cache untuk menghindari loop CIDR berulang di high-traffic.
* – [PERF] send_security_headers(): headers_list() di-cache per-request (by reference) untuk mengurangi iterasi.
* – [UX][FIX] block_author_archives(): Fallback kini menggunakan theme 404 template dengan wp_die() sebagai backup native.
* – [FIX] Regex validasi CSP source kini menggunakan karakter raw < > (bukan HTML entities).
* – [FIX] preg_quote() pada directive CSP custom sekarang menggunakan delimiter ‘/’.
* – [FIX] Typo UI extra closing tag pada Remove Query Strings dibersihkan.
*
* Changelog 3.2.0 (Production Hardening & Bugfixes):
* – [SEC][FIX] apply_custom_csp_exceptions(): tolak karakter `;` di source token untuk mencegah directive injection.
* – [COMPAT][FIX] Login Stealth: validasi scalar/string untuk input ‘action’, ‘provider’, dan cookie ‘wuss_admin_flow’. Mencegah fatal error di PHP 8+.
* – [BUG][FIX] check_ssl_force(): perbaiki double path redirect untuk WordPress yang terinstall di subdirectory.
* – [COMPAT][FIX] Login Stealth: izinkan action ‘register’ secara otomatis jika opsi users_can_register aktif.
*
* Changelog 3.1.9 (CSP Parser v3 – Directive-Aware Semicolon + Preservative Sanitizer):
* – [BUG][FIX] apply_custom_csp_exceptions(): semicolon split sekarang directive-aware.
* – [BUG][FIX] Token validation dilonggarkan untuk source CSP valid.
* – [COMPAT][FIX] Sanitizer Raw CSP sekarang preservative (bukan sanitize_textarea_field).
* – [CACHE] Bump ‘builder’ => ‘csp-parser-v3’ di get_csp_cache_key() fingerprint.
* – [SEC][DOCS] handle_manual_save(): tambah komentar tegas “JANGAN pakai wp_cache_flush_group”.
* – [UX] UI Raw CSP Directives: deskripsi diperbarui.
*
* Changelog 3.1.8 (CSP Parser v2 Polish + OIDC Compatibility):
* – [UX][FIX] UI Raw CSP Directives: deskripsi diperbarui.
* – [COMPAT][FIX] Login Stealth: provider di-lowercase dengan strtolower().
* – [COMPAT][FIX] Login Stealth: tambah id_token ke has_token_like.
* – [DEBUG][FIX] send_security_headers(): debug log CSP di-guard dengan constant WUSS_DEBUG_CSP.
* – [VERSION] Bump ke 3.1.8.
*
* Changelog 3.1.7 (CSP Module Enhancement – Backport from 3.7.5 + Hardening Final):
* – [NEW] CSP Mode dropdown: strict / compatible / report_only (default: strict).
* – [NEW] get_csp_cache_key() berbasis fingerprint.
* – [NEW] apply_custom_csp_exceptions() helper.
* – [IMPROVED] build_admin_csp() dan build_frontend_csp() sekarang memakai array-map.
* – [IMPROVED] send_security_headers(): pilih header berdasarkan csp_mode.
* – [IMPROVED] Tambah filter wuss_csp_report_uri.
* – [IMPROVED] Security headers via array loop.
* – [IMPROVED] send_security_headers() cek headers_sent().
* – [SEC] Cache key fingerprint pakai ssl_active = is_ssl() || is_forwarded_https_trusted().
* – [SEC] report_uri divalidasi dengan esc_url_raw().
* – [COMPAT][SEC] Login Stealth: OAuth/Social Login bypass diperketat.
* – [FIX] Login Stealth: hapus blok POST redirect_to yang redundant.
* – [COMPAT][FIX] secure_uploads_directory(): logic writable check diperbaiki.
* – [UX] UI: tambah dropdown CSP Mode.
* – [UX] Sanitizer: validasi csp_mode dengan whitelist.
*
* Changelog 3.1.7 post-release (CSP Parser v2 + Cache Bust):
* – [BUG][FIX] apply_custom_csp_exceptions(): parser v2 lebih toleran.
* – [BUG][FIX] get_csp_cache_key(): tambah ‘builder’ => ‘csp-parser-v2’.
* – [DEBUG] send_security_headers(): tambah error_log CSP header sementara.
*
* Catatan: HANYA modul CSP yang di-backport dari 3.7.5.
*
* Changelog 3.1.6 (Hardened Production Patch – Final):
* – [P0][FIX] Race condition pada Redis counter: cache_incr_with_ttl().
* – [P0][SEC] .htaccess Force SSL: ganti %{HTTP_HOST} dengan canonical host dari home_url().
* – [P1][SEC] Login Stealth: hapus bypass Referer, wordpress_test_cookie, dan parameter generik.
* – [P1][SEC] Login Stealth: tambahkan signed short-lived cookie (wuss_admin_flow).
* – [P1][SEC] Login Stealth: POST bypass redirect_to=/wp-admin butuh wuss_admin_flow cookie.
* – [P1][FIX] HSTS dan CSP upgrade-insecure-requests sekarang proxy-aware.
* – [P1][FIX][BUG] CSP custom exceptions: refactor ke array-map merge.
* – [P1][UX] Samakan deskripsi UI CSP dengan whitelist directive aktual.
* – [P2][UX] .htaccess backup satu kali (.htaccess.wuss-backup).
* – [P2][UX] Warning UI tegas untuk .htaccess Force SSL + Proxy.
* – [P2][UX] Filter wuss_apply_session_timeout.
* – [P2][UX] Rename label “Mixed Content Mitigation” menjadi “Mixed Content Mitigation via CSP”.
* – [P2][UX] Tambah UI row untuk Force SSL .htaccess.
* – [P2][UX] settings_errors disimpan ke transient sebelum redirect.
* – [P2][COMPAT] update_htaccess: cek writable home dir HANYA saat .htaccess belum ada.
*
* Changelog 3.1.5 (Final Hardening Patch):
* – [CRITICAL] cache_incr_locked(): Implemented “Near-Threshold Fail-Closed” heuristic.
* – [FIX] Lifecycle: Cleanup uploads/.htaccess checks file writability.
* – [FIX] uninstall() now properly cleans up root .htaccess marker.
* – [UX] Login Stealth: Added explicit admin warning regarding direct /wp-login.php access.
*
* Changelog 3.1.4:
* – [FIX] Login Stealth: Reintroduce wp_validate_redirect() bypass for /wp-admin.
* – [FIX] .htaccess SSL: Reverted to safe AND logic.
* – [FIX] strpos() strict check for ver= removal.
* – [FIX] Lifecycle cleanup for uploads/.htaccess.
* – [SEC] BF test_mode strictly read-only.
* – [FIX] RL internal bypass (admin/ajax/cron/rest).
* – [PERF] BF lock wait reduced to 30ms.
*/
class WP_Ultimate_SSL_Security
{
private $option_name = “wuss_settings”;
private $options = null;
private $use_object_cache = null;
private $ip_address = null;
private $rest_path = null;
private static $default_options = null;
private $parsed_whitelist = null;

// [3.2.1] Request-local cache untuk trusted proxy & sent headers
private $trusted_proxy_cache = [];
private $sent_headers_cache = null;
// [3.2.3 Perf] Request-local cache untuk hasil HTTPS trust & daftar proxy
private $is_forwarded_https_cache = null;
private $trusted_proxies_list = null;

public function __construct()
{
$this->options = $this->get_options();
$this->use_object_cache = $this->check_object_cache();

if (!$this->use_object_cache) {
$this->options[‘enable_rate_limit’] = 0;
$this->options[‘enable_brute_force’] = 0;
}

if (defined(“WP_CLI”) && WP_CLI && class_exists(“WP_CLI”)) {
\WP_CLI::add_command(“wuss”, [$this, “cli_handler”]);
}

add_action(“admin_menu”, [$this, “add_plugin_page”]);
add_action(“plugins_loaded”, [$this, “setup_runtime_constants”], 0);
add_action(“plugins_loaded”, [$this, “maybe_run_migrations”], 1);
add_action(“plugins_loaded”, [$this, “check_compatibility”], 5);

if (!empty($this->options[“enable_rate_limit”])) {
add_action(“init”, [$this, “check_rate_limit”], 5);
}
if (!empty($this->options[“optimize_heartbeat”])) {
add_action(“init”, [$this, “control_heartbeat”], 10);
}
if (!empty($this->options[“enable_brute_force”])) {
add_filter(“authenticate”, [$this, “check_brute_force_block”], 30, 3);
add_action(“wp_login_failed”, [$this, “log_failed_attempt”], 10, 1);
add_action(“wp_login”, [$this, “reset_login_attempts”], 10, 2);
}
if (!empty($this->options[“enable_login_stealth”])) {
add_action(“init”, [$this, “maybe_mark_admin_login_flow”], 0);
add_action(“login_init”, [$this, “login_stealth_mode”]);
}
if (!empty($this->options[“block_user_enum_strict”])) {
add_filter(“rest_endpoints”, [$this, “filter_rest_endpoints_users”]);
add_action(“template_redirect”, [$this, “block_author_archives”], 4);
}
add_action(“admin_init”, [$this, “init_admin_save”], 0);

add_action(“init”, [$this, “apply_hardening”], 10);
add_action(“init”, [$this, “check_ssl_force”], 0);
add_action(“template_redirect”, [$this, “block_user_enumeration”], 5);
add_action(“send_headers”, [$this, “send_security_headers”], 1);
add_filter(“auth_cookie_expiration”, [$this, “set_admin_expiration”], 99, 3);
add_filter(“rest_authentication_errors”, [$this, “restrict_rest_global_for_guests”], 5);
add_filter(“rest_post_collection_params”, [$this, “limit_rest_per_page”], 10, 1);
add_action(“admin_notices”, [$this, “admin_notices”]);
}

public static function get_default_options_static()
{
return [
“enable_ssl” => 0, “force_ssl” => 0, “force_ssl_htaccess” => 0,
“mixed_content_fixer” => 0, “disable_xmlrpc” => 0, “hide_version” => 0,
“block_user_enum” => 0, “disable_rest_api” => 0, “auto_logout_admin” => 0,
“clean_head_meta” => 0, “enable_brute_force” => 0, “security_headers” => 0,
“block_sensitive_files” => 0, “disable_file_edit” => 0, “disable_file_mods” => 0,
“optimize_heartbeat” => 0,
“disable_directory_browsing” => 0, “block_bad_queries_htaccess” => 0,
“enable_rate_limit” => 0, “block_public_feeds” => 0, “enable_proxy_trust” => 0,
“rate_limit_max” => 180, “rate_limit_seconds” => 60,
“heartbeat_frontend” => 60, “heartbeat_backend” => 30,
“bf_max_attempts” => 10, “bf_lockout_duration” => 60, “bf_whitelist” => “”,
“csp_custom_exceptions” => “”, “csp_mode” => “strict”,
“hsts_preload” => 0, “hsts_include_subdomains” => 0,
“enable_logging” => 0, “enable_login_stealth” => 0, “block_user_enum_strict” => 0,
“obfuscate_login_errors” => 0, “disable_app_passwords” => 0,
“secure_uploads_htaccess” => 0, “remove_query_strings” => 0,
“admin_session_duration” => 14400, “test_mode” => 0,
];
}

private function get_default_options() { return self::get_default_options_static(); }

/* ==========================================================================
HELPERS
========================================================================== */

private function is_forwarded_https_trusted()
{
// [3.2.3 Perf] Return cached result jika sudah dihitung di request ini
if ($this->is_forwarded_https_cache !== null) {
return $this->is_forwarded_https_cache;
}

$remote_ip = “”;
if (!empty($_SERVER[“REMOTE_ADDR”])) {
$remote_ip = filter_var($_SERVER[“REMOTE_ADDR”], FILTER_VALIDATE_IP);
}

$this->is_forwarded_https_cache = (bool) (
$remote_ip &&
!empty($this->options[“enable_proxy_trust”]) &&
$this->is_trusted_proxy($remote_ip) &&
!empty($_SERVER[“HTTP_X_FORWARDED_PROTO”]) &&
strtolower($_SERVER[“HTTP_X_FORWARDED_PROTO”]) === “https”
);

return $this->is_forwarded_https_cache;
}

private function sanitize_log_text($text)
{
$text = (string) $text;
$text = preg_replace(‘/[\r\n\t]+/’, ” “, $text);
return substr($text, 0, 500);
}

/* ==========================================================================
UNIVERSAL OBJECT CACHE LAYER
========================================================================== */

private function check_object_cache()
{
$has_oc = function_exists(“wp_using_ext_object_cache”) && wp_using_ext_object_cache();
if (defined(“WUSS_FORCE_REDIS”) && WUSS_FORCE_REDIS) {
if (!$has_oc) {
add_action(“admin_notices”, function () {
echo ‘<div class=”notice notice-error”><p><strong>’ .
esc_html__(“WUSS:”, “wp-ultimate-ssl-security”) . “</strong> ” .
esc_html__(“WUSS_FORCE_REDIS is set but no external object cache is detected.”, “wp-ultimate-ssl-security”) .
“</p></div>”;
});
return false;
}
return true;
}
return $has_oc;
}

private function cache_get($key) { $val = wp_cache_get($key, WUSS_CACHE_GROUP); return $val !== false ? $val : false; }
private function cache_set($key, $value, $ttl) { return wp_cache_set($key, $value, WUSS_CACHE_GROUP, $ttl); }
private function cache_delete($key) { wp_cache_delete($key, WUSS_CACHE_GROUP); }
private function generate_cache_key($prefix, $identifier) { return $prefix . “_” . md5($identifier); }

private function cache_incr_with_ttl($key, $ttl)
{
wp_cache_add($key, 0, WUSS_CACHE_GROUP, $ttl);
$count = wp_cache_incr($key, 1, WUSS_CACHE_GROUP);

if ($count === false) {
// [3.2.2] Fallback aman: jangan reset counter existing ke 1.
// Beberapa object-cache backend/drop-in bisa gagal wp_cache_incr()
// meskipun key sudah ada. Dalam kondisi itu, baca nilai existing dulu.
$existing = wp_cache_get($key, WUSS_CACHE_GROUP);

if ($existing === false) {
$this->cache_set($key, 1, $ttl);
return 1;
}

$next = max(1, (int) $existing + 1);
$this->cache_set($key, $next, $ttl);
return $next;
}

return (int) $count;
}

/* ==========================================================================
OPTIONS & MIGRATIONS
========================================================================== */

private function get_options()
{
if ($this->options === null) {
$this->options = wp_parse_args(get_option($this->option_name, []), $this->get_default_options());
}
return $this->options;
}

public function check_compatibility()
{
global $wp_version;
$errors = [];
if (version_compare(PHP_VERSION, WUSS_MIN_PHP_VERSION, “<“)) {
$errors[] = sprintf(__(“Plugin memerlukan PHP %s+. Versi Anda: %s”, “wp-ultimate-ssl-security”), WUSS_MIN_PHP_VERSION, PHP_VERSION);
}
if (version_compare($wp_version, WUSS_MIN_WP_VERSION, “<“)) {
$errors[] = sprintf(__(“Plugin memerlukan WP %s+. Versi Anda: %s”, “wp-ultimate-ssl-security”), WUSS_MIN_WP_VERSION, $wp_version);
}
if (!empty($errors)) {
add_action(“admin_notices”, function () use ($errors) {
echo ‘<div class=”error”>’;
foreach ($errors as $error) echo “<p>” . esc_html($error) . “</p>”;
echo “</div>”;
});
}
}

public function maybe_run_migrations()
{
if (get_option(“wuss_plugin_version”, “”) === WUSS_VERSION) return;
global $wpdb;
$wpdb->update($wpdb->options, [“autoload” => “no”], [“option_name” => $this->option_name], [“%s”], [“%s”]);

// [Final] Hapus dead code cache_delete lama, ganti dengan bump version
$this->bump_csp_cache_version();

delete_option(“wuss_file_monitor_last”);
update_option(“wuss_settings”, wp_parse_args(get_option(“wuss_settings”, []), self::get_default_options_static()));
update_option(“wuss_plugin_version”, WUSS_VERSION);
$this->options = get_option(“wuss_settings”, []);
}

public function setup_runtime_constants()
{
if (!empty($this->options[“disable_file_edit”]) && !defined(“DISALLOW_FILE_EDIT”)) define(“DISALLOW_FILE_EDIT”, true);
if (!empty($this->options[“disable_file_mods”]) && !defined(“DISALLOW_FILE_MODS”)) define(“DISALLOW_FILE_MODS”, true);
}

/* ==========================================================================
IP DETECTION & PROXY TRUST
========================================================================== */

/**
* [3.2.1] Request-local cache untuk menghindari loop CIDR berulang.
*/
private function is_trusted_proxy($remote_ip)
{
$remote_ip = (string) $remote_ip;
if ($remote_ip === ”) {
return false;
}

// Cek cache per-IP
if (isset($this->trusted_proxy_cache[$remote_ip])) {
return $this->trusted_proxy_cache[$remote_ip];
}

// [3.2.3 Perf] Cache daftar proxy agar apply_filters tidak berulang
if ($this->trusted_proxies_list === null) {
$this->trusted_proxies_list = apply_filters(“wuss_trusted_proxy_ips”, [
“127.0.0.1”, “::1”,
“173.245.48.0/20”, “103.21.244.0/22”, “103.22.200.0/22”,
“103.31.4.0/22”, “141.101.64.0/18”, “108.162.192.0/18”,
“190.93.240.0/20”, “188.114.96.0/20”, “197.234.240.0/22”,
“198.41.128.0/17”, “162.158.0.0/15”, “104.16.0.0/13”,
“104.24.0.0/14”, “172.64.0.0/13”, “131.0.72.0/22”,
“2400:cb00::/32”, “2606:4700::/32”, “2803:f800::/32”,
“2405:b500::/32”, “2405:8100::/32”, “2a06:98c0::/29”, “2c0f:f248::/32”,
]);
}

foreach ($this->trusted_proxies_list as $entry) {
if ($this->ip_matches_cidr($remote_ip, $entry)) {
return $this->trusted_proxy_cache[$remote_ip] = true;
}
}

return $this->trusted_proxy_cache[$remote_ip] = false;
}

private function ip_matches_cidr($ip, $cidr)
{
if (strpos($cidr, “/”) === false) return $ip === $cidr;
list($subnet, $bits) = explode(“/”, $cidr, 2);
$bits = (int) $bits;
$ip_bin = inet_pton($ip);
$subnet_bin = inet_pton($subnet);
if ($ip_bin === false || $subnet_bin === false) return false;
if (strlen($ip_bin) !== strlen($subnet_bin)) return false;
$max_bits = strlen($ip_bin) * 8;
if ($bits < 0 || $bits > $max_bits) return false;
$full_bytes = intdiv($bits, 8);
if ($full_bytes > 0 && substr($ip_bin, 0, $full_bytes) !== substr($subnet_bin, 0, $full_bytes)) return false;
$remainder = $bits % 8;
if ($remainder === 0) return true;
$mask = chr((0xff << (8 – $remainder)) & 0xff);
return (ord($ip_bin[$full_bytes]) & ord($mask)) === (ord($subnet_bin[$full_bytes]) & ord($mask));
}

private function get_user_ip()
{
if ($this->ip_address !== null) return $this->ip_address;
$ip = “0.0.0.0”;
$remote_ip = “”;

if (!empty($_SERVER[“REMOTE_ADDR”]) && ($remote_ip = filter_var($_SERVER[“REMOTE_ADDR”], FILTER_VALIDATE_IP))) {
$ip = $remote_ip;
}

if (!empty($this->options[“enable_proxy_trust”]) && $this->is_trusted_proxy($remote_ip)) {
if (!empty($_SERVER[“HTTP_CF_CONNECTING_IP”])) {
// [Final] Paksa validasi Public IP untuk CF header
$cf_ip = filter_var($_SERVER[“HTTP_CF_CONNECTING_IP”], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if (apply_filters(“wuss_trust_cloudflare_header”, true, $remote_ip, $cf_ip) && $cf_ip) {
$ip = $cf_ip;
}
} elseif (!empty($_SERVER[“HTTP_X_FORWARDED_FOR”])) {
// [3.2.3 Perf] XFF parsing: Baca kanan ke kiri, optimasi filter_var
$xff = array_reverse(array_map(“trim”, explode(“,”, (string) $_SERVER[“HTTP_X_FORWARDED_FOR”])));

foreach ($xff as $f_ip) {
// 1. Cek langsung apakah Public IP (menghemat 1x filter_var jika ternyata public)
$public_ip = filter_var(
$f_ip,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
);

if ($public_ip) {
// Pastikan IP Publik ini bukan milik Trusted Proxy (beberapa CDN pakai IP Publik)
if (!$this->is_trusted_proxy($public_ip)) {
$ip = $public_ip;
break;
}
continue; // Ini trusted proxy hop, lanjut ke IP berikutnya di kiri
}

// 2. Jika bukan Public IP, cek apakah format IP valid (Private/Reserved)
$valid_ip = filter_var($f_ip, FILTER_VALIDATE_IP);
if (!$valid_ip) {
continue; // String bukan format IP, skip
}

// 3. Jika IP Private/Reserved, cek apakah ini Trusted Proxy Hop
if ($this->is_trusted_proxy($valid_ip)) {
continue; // Ini hop proxy tepercaya, lanjut
}

// 4. Jika valid, private, tapi BUKAN trusted proxy -> Ini IP Klien Asli
$ip = $valid_ip;
break;
}
}
}
return $this->ip_address = $ip;
}

/* ==========================================================================
SECURITY HEADERS & CSP
========================================================================== */

/**
* [3.2.1] headers_list() di-cache per-request (return by reference) untuk performa high-traffic.
*/
private function &get_sent_headers()
{
if ($this->sent_headers_cache === null) {
$this->sent_headers_cache = [];
foreach (headers_list() as $h) {
$pos = strpos($h, “:”);
if ($pos !== false) {
$this->sent_headers_cache[strtolower(trim(substr($h, 0, $pos)))] = true;
}
}
}
return $this->sent_headers_cache;
}

public function send_security_headers()
{
if (headers_sent()) return;
if (empty($this->options[“security_headers”]) || wp_doing_ajax() || (defined(“REST_REQUEST”) && REST_REQUEST)) return;
if (!is_admin()) { $accept = $_SERVER[“HTTP_ACCEPT”] ?? “”; if ($accept && stripos($accept, “text/html”) === false) return; }

$sent =& $this->get_sent_headers();

$headers = apply_filters(‘wuss_security_headers’, [
‘X-Content-Type-Options’ => ‘nosniff’,
‘X-Frame-Options’ => ‘SAMEORIGIN’,
‘Referrer-Policy’ => ‘strict-origin-when-cross-origin’,
‘Permissions-Policy’ => ‘geolocation=(), microphone=(), camera=()’,
]);
foreach ($headers as $name => $value) {
$name_lower = strtolower($name);
if (!isset($sent[$name_lower])) {
header($name . ‘: ‘ . $value);
$sent[$name_lower] = true;
}
}

$ssl_active = is_ssl() || $this->is_forwarded_https_trusted();
if ($ssl_active) {
$hsts = “max-age=31536000”;
if (!empty($this->options[“hsts_include_subdomains”]) || !empty($this->options[“hsts_preload”])) $hsts .= “; includeSubDomains”;
if (!empty($this->options[“hsts_preload”])) $hsts .= “; preload”;

$hsts_lower = strtolower(“Strict-Transport-Security”);
if (!isset($sent[$hsts_lower])) {
header(“Strict-Transport-Security: ” . $hsts);
$sent[$hsts_lower] = true;
}
}

$context = is_admin() ? “admin” : “front”;
$cache_key = $this->get_csp_cache_key($context);
$csp = $this->cache_get($cache_key);
if ($csp === false) {
$csp = is_admin() ? $this->build_admin_csp() : $this->build_frontend_csp();
$this->cache_set($cache_key, $csp, 6 * HOUR_IN_SECONDS);
}

$csp_mode = $this->options[“csp_mode”] ?? “strict”;
$header_name = ($csp_mode === “report_only”)
? “Content-Security-Policy-Report-Only”
: “Content-Security-Policy”;

$csp_lower = strtolower($header_name);
if (apply_filters(“wuss_send_csp_header”, true, $csp, $context) && !isset($sent[$csp_lower])) {
if (defined(“WUSS_DEBUG_CSP”) && WUSS_DEBUG_CSP
&& defined(“WP_DEBUG_LOG”) && WP_DEBUG_LOG) {
error_log(“WUSS CSP HEADER [$header_name]: ” . $csp);
}
header($header_name . “: ” . $csp);
$sent[$csp_lower] = true; // [3.2.1] Tandai cache lokal bahwa header sudah dikirim
}
}

private function get_csp_cache_key($context)
{
$ssl_active = is_ssl() || $this->is_forwarded_https_trusted();
$report_uri = esc_url_raw(apply_filters(‘wuss_csp_report_uri’, ”));

$fingerprint = [
‘version’ => WUSS_VERSION,
‘cache_ver’ => get_option(“wuss_csp_cache_version”, “1”),
‘builder’ => ‘csp-parser-v4’, // [3.2.1] Bump builder
‘context’ => $context,
‘ssl_active’ => $ssl_active ? 1 : 0,
‘mixed’ => !empty($this->options[“mixed_content_fixer”]) ? 1 : 0,
‘mode’ => $this->options[“csp_mode”] ?? ‘strict’,
‘exceptions’ => $this->options[“csp_custom_exceptions”] ?? ”,
‘report_uri’ => $report_uri,
];
return $this->generate_cache_key(“csp”, wp_json_encode($fingerprint));
}

private function bump_csp_cache_version()
{
update_option(“wuss_csp_cache_version”, (string) time(), false);
}

/**
* [3.2.0] Merge custom CSP exceptions ke directive existing (Parser v3).
* [3.2.1] Tolak `;` di level token, pastikan preg_quote pakai delimiter, pastikan raw < > dipakai.
*/
private function apply_custom_csp_exceptions($directives)
{
if (empty($this->options[“csp_custom_exceptions”])) {
return $directives;
}

$allowed_dirs = apply_filters(“wuss_allowed_csp_custom_directives”, [
‘script-src’ => 1,
‘style-src’ => 1,
‘img-src’ => 1,
‘connect-src’ => 1,
‘font-src’ => 1,
‘frame-src’ => 1,
‘media-src’ => 1,
‘worker-src’ => 1,
‘child-src’ => 1,
‘form-action’ => 1,
]);

$raw = (string) $this->options[“csp_custom_exceptions”];
$raw = str_replace([“\r\n”, “\r”], “\n”, $raw);

$directive_names = array_map(function ($dir) {
return preg_quote((string) $dir, ‘/’);
}, array_keys($allowed_dirs));

$directive_pattern = implode(‘|’, $directive_names);
$raw = preg_replace(
‘/;\s*(?=(?:’ . $directive_pattern . ‘)\s+)/i’,
“\n”,
$raw
);

$lines = array_filter(array_map(‘trim’, explode(“\n”, $raw)));

foreach ($lines as $line) {
if ($line === ”) {
continue;
}

if (preg_match(‘/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/’, $line)) {
continue;
}

if (!preg_match(‘/^([a-zA-Z-]+)\s+(.+)$/’, $line, $matches)) {
continue;
}

$dir = strtolower(trim($matches[1]));

if (!isset($allowed_dirs[$dir])) {
continue;
}

$vals = preg_split(‘/\s+/’, trim($matches[2]));

foreach ($vals as $v) {
$v = trim($v);

if ($v === ”) {
continue;
}

// [3.2.1 Final] Pastikan memakai raw < > (bukan HTML entities). Tolak ; di source.
if (preg_match(‘/[\x00-\x1F\x7F<>”;]/’, $v)) {
continue;
}

if (!isset($directives[$dir])) {
$directives[$dir] = [];
}

$directives[$dir][] = $v;
}
}

return $directives;
}

private function render_csp_directives($directives)
{
$p = [];
foreach ($directives as $dir => $vals) {
$vals = array_unique($vals);
if (empty($vals)) {
$p[] = $dir;
} else {
$p[] = $dir . ” ” . implode(” “, $vals);
}
}
return implode(“; “, $p);
}

private function build_admin_csp()
{
$directives = [
“default-src” => [“‘self'”],
“script-src” => [“‘self'”, “‘unsafe-inline'”, “‘unsafe-eval'”, “data:”, “blob:”],
“style-src” => [“‘self'”, “‘unsafe-inline'”, “https:”],
“img-src” => [“‘self'”, “data:”, “blob:”, “https:”],
“connect-src” => [“‘self'”, “data:”, “blob:”, “https:”],
“font-src” => [“‘self'”, “data:”, “https:”],
“frame-src” => [“‘self'”],
“base-uri” => [“‘self'”],
“form-action” => [“‘self'”],
“frame-ancestors” => [“‘self'”],
“object-src” => [“‘none'”],
];

$domains = [
“*.wordpress.org”, “*.google.com”, “*.gstatic.com”, “*.gravatar.com”,
“*.wp.com”, “*.youtube.com”, “*.vimeo.com”,
];
foreach ($domains as $d) {
foreach ([“script-src”, “style-src”, “img-src”, “connect-src”, “frame-src”] as $dir) {
$directives[$dir][] = $d;
}
}

$directives = $this->apply_custom_csp_exceptions($directives);

$ssl_active = is_ssl() || $this->is_forwarded_https_trusted();
if ($ssl_active || !empty($this->options[“mixed_content_fixer”])) {
$directives[“upgrade-insecure-requests”] = [];
}

$report_uri = esc_url_raw(apply_filters(‘wuss_csp_report_uri’, ”));
if (!empty($report_uri)) {
$directives[“report-uri”] = [$report_uri];
}

return $this->render_csp_directives($directives);
}

private function build_frontend_csp()
{
$csp_mode = $this->options[“csp_mode”] ?? “strict”;

if ($csp_mode === “compatible”) {
$directives = [
“default-src” => [“‘self'”],
“object-src” => [“‘none'”],
“base-uri” => [“‘self'”],
“form-action” => [“‘self'”],
“frame-ancestors” => [“‘self'”],
“script-src” => [“‘self'”, “‘unsafe-inline'”, “https:”, “blob:”],
“style-src” => [“‘self'”, “‘unsafe-inline'”, “https:”, “data:”],
“img-src” => [“‘self'”, “data:”, “https:”, “blob:”],
“connect-src” => [“‘self'”, “https:”, “data:”, “blob:”],
“frame-src” => [“‘self'”, “https:”],
“font-src” => [“‘self'”, “data:”, “https:”],
];
} else {
$directives = [
“default-src” => [“‘self'”],
“object-src” => [“‘none'”],
“base-uri” => [“‘self'”],
“form-action” => [“‘self'”],
“frame-ancestors” => [“‘self'”],
“script-src” => [“‘self'”, “‘unsafe-inline'”],
“style-src” => [“‘self'”, “‘unsafe-inline'”, “https:”],
“img-src” => [“‘self'”, “data:”, “https:”],
“connect-src” => [“‘self'”, “https:”],
“frame-src” => [“‘self'”],
“font-src” => [“‘self'”, “data:”, “https:”],
];
}

$directives = $this->apply_custom_csp_exceptions($directives);

$ssl_active = is_ssl() || $this->is_forwarded_https_trusted();
if ($ssl_active || !empty($this->options[“mixed_content_fixer”])) {
$directives[“upgrade-insecure-requests”] = [];
}

$report_uri = esc_url_raw(apply_filters(‘wuss_csp_report_uri’, ”));
if (!empty($report_uri)) {
$directives[“report-uri”] = [$report_uri];
}

return $this->render_csp_directives($directives);
}

/* ==========================================================================
RATE LIMITING
========================================================================== */

public function check_rate_limit()
{
if (is_admin() || wp_doing_ajax() || wp_doing_cron() || (defined(‘REST_REQUEST’) && REST_REQUEST)) {
return;
}

$uri = $_SERVER[“REQUEST_URI”] ?? “”;
if (preg_match(‘#\.(css|js|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|eot|map)(\?|$)#i’, $uri)) {
return;
}

$ip = $this->get_user_ip();
if ($this->is_ip_whitelisted($ip)) return;

if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) return;

$max = absint($this->options[“rate_limit_max”]);
$window = absint($this->options[“rate_limit_seconds”]);
if ($max < 1 || $window < 1) return;

if (!empty($this->options[“test_mode”])) {
$this->log_security_event(“test_mode_rate_limit”, $ip, “Would rate limit”);
return;
}

$cache_key = $this->generate_cache_key(“rl”, $ip);

$count = $this->cache_incr_with_ttl($cache_key, $window + 10);

if ((int)$count > $max) {
$this->rate_limit_block($ip, $count, $max, $window);
}
}

private function rate_limit_block($ip, $count, $max, $window)
{
$this->log_security_event(“rate_limit_blocked”, $ip, “Count: {$count}, Max: {$max}”);
status_header(429);
header(“Retry-After: ” . $window);
die(“Too many requests.”);
}

private function log_security_event($type, $ip, $details = “”)
{
if (!empty($this->options[“enable_logging”]) && defined(“WP_DEBUG_LOG”) && WP_DEBUG_LOG) {
$type = $this->sanitize_log_text($type);
$ip = $this->sanitize_log_text($ip);
$details = $this->sanitize_log_text($details);
error_log(sprintf(“[%s] WUSS: %s from %s – %s”, gmdate(‘c’), $type, $ip, $details));
}
}

/* ==========================================================================
SSL & HARDENING
========================================================================== */

private function enable_ssl_urls()
{
if (!$this->is_ssl_possible()) { add_settings_error(“wuss_notices”, “ssl_warning”, __(“Peringatan: SSL mungkin tidak tersedia di server ini.”, “wp-ultimate-ssl-security”), “warning”); return; }
$su = get_option(“siteurl”); $hu = get_option(“home”);
update_option(“siteurl”, $this->force_https_url($su));
update_option(“home”, $this->force_https_url($hu));
}

private function force_https_url($url)
{
$url = (string) $url;
$parts = wp_parse_url($url);

if (empty($parts[“scheme”]) || strtolower($parts[“scheme”]) !== “http”) {
return $url;
}

return preg_replace(“#^http://#i”, “https://”, $url, 1);
}

private function is_ssl_possible()
{
return (isset($_SERVER[“SERVER_PORT”]) && (int) $_SERVER[“SERVER_PORT”] === 443)
|| (isset($_SERVER[“HTTPS”]) && strtolower($_SERVER[“HTTPS”]) !== “off”)
|| $this->is_forwarded_https_trusted();
}

/**
* [3.2.1] Hardening canonical host regex dan validasi $request_uri path.
*/
public function check_ssl_force()
{
if (empty($this->options[“force_ssl”])) return;
if (is_ssl() || $this->is_forwarded_https_trusted()) return;

$request_uri = $_SERVER[“REQUEST_URI”] ?? “/”;
if ($request_uri === ” || $request_uri[0] !== ‘/’ || preg_match(‘/[\r\n]/’, $request_uri)) {
$request_uri = ‘/’;
}

$home = wp_parse_url(home_url());

if (empty($home[“host”]) || !preg_match(‘/^[a-z0-9.-]+$/i’, $home[“host”])) {
return;
}

$port = !empty($home[“port”]) ? “:” . absint($home[“port”]) : “”;
$target = “https://” . $home[“host”] . $port . $request_uri;

wp_safe_redirect($target, 301);
exit;
}

public function apply_hardening()
{
if (!empty($this->options[“disable_xmlrpc”])) add_filter(“xmlrpc_enabled”, “__return_false”);
if (!empty($this->options[“hide_version”])) { remove_action(“wp_head”, “wp_generator”); add_filter(“the_generator”, “__return_empty_string”); }
if (!empty($this->options[“clean_head_meta”])) { remove_action(“wp_head”, “rsd_link”); remove_action(“wp_head”, “wlwmanifest_link”); }
if (!empty($this->options[“block_public_feeds”])) {
foreach([“do_feed”,”do_feed_rdf”,”do_feed_rss”,”do_feed_rss2″,”do_feed_atom”] as $hook) add_action($hook, [$this, “disable_feed_render”], 1);
remove_action(“wp_head”, “feed_links_extra”, 3); remove_action(“wp_head”, “feed_links”, 2);
}
if (!empty($this->options[“block_user_enum_strict”])) add_filter(“author_rewrite_rules”, “__return_empty_array”);
if (!empty($this->options[“obfuscate_login_errors”])) add_filter(“login_errors”, function() { return “<strong>ERROR:</strong> ” . __(“Username atau password yang Anda masukkan salah.”, “wp-ultimate-ssl-security”); });
if (!empty($this->options[“disable_app_passwords”])) add_filter(“wp_is_application_passwords_available”, “__return_false”);
if (!empty($this->options[“remove_query_strings”])) { add_filter(“style_loader_src”, [$this, “remove_version_query_arg”], 9999); add_filter(“script_loader_src”, [$this, “remove_version_query_arg”], 9999); }
}

public function remove_version_query_arg($src) { return (strpos($src, “ver=”) !== false) ? remove_query_arg(“ver”, $src) : $src; }
public function disable_feed_render() { status_header(410); die(“Content feeds are disabled.”); }
public function control_heartbeat() { add_filter(“heartbeat_settings”, [$this, “heartbeat_settings_filter”]); }
public function heartbeat_settings_filter($s) { global $pagenow; $s[“interval”] = is_admin() ? (in_array($pagenow, [“post.php”,”post-new.php”], true) ? 15 : absint($this->options[“heartbeat_backend”])) : absint($this->options[“heartbeat_frontend”]); $s[“interval”] = max(15, min(300, $s[“interval”])); return $s; }

public function block_user_enumeration()
{
if (empty($this->options[“block_user_enum”]) || is_admin()) return;
if (($a = filter_input(INPUT_GET, “author”, FILTER_VALIDATE_INT)) !== false && $a > 0) { $this->log_security_event(“enumeration_blocked”, $this->get_user_ip(), “Attempted author ID: $a”); wp_safe_redirect(home_url(“/”)); exit; }
}

/* ==========================================================================
LOGIN STEALTH (HARDENED IN 3.1.6)
========================================================================== */

private function wuss_login_flow_token($bucket = null)
{
$ip = $this->get_user_ip();
$ua = $_SERVER[‘HTTP_USER_AGENT’] ?? ”;
$bucket = $bucket !== null ? $bucket : floor(time() / 300);
return hash_hmac(‘sha256’, $ip . ‘|’ . $ua . ‘|’ . $bucket, wp_salt(‘auth’));
}

public function maybe_mark_admin_login_flow()
{
if (is_user_logged_in()) return;

$path = parse_url($_SERVER[‘REQUEST_URI’] ?? ”, PHP_URL_PATH) ?: ”;
$admin_path = parse_url(admin_url(), PHP_URL_PATH) ?: ‘/wp-admin/’;

if (strpos(trailingslashit($path), trailingslashit($admin_path)) === 0) {
setcookie(
‘wuss_admin_flow’,
$this->wuss_login_flow_token(),
[
‘expires’ => time() + 300,
‘path’ => COOKIEPATH ?: ‘/’,
‘secure’ => is_ssl() || $this->is_forwarded_https_trusted(),
‘httponly’ => true,
‘samesite’ => ‘Lax’,
]
);
}
}

private function has_valid_admin_flow_cookie()
{
if (empty($_COOKIE[‘wuss_admin_flow’]) || !is_string($_COOKIE[‘wuss_admin_flow’])) {
return false;
}

$token = $_COOKIE[‘wuss_admin_flow’];

if (strlen($token) !== 64 || !ctype_xdigit($token)) {
return false;
}

$current = $this->wuss_login_flow_token();
$previous = $this->wuss_login_flow_token(floor((time() – 300) / 300));
return hash_equals($current, $token) || hash_equals($previous, $token);
}

/**
* [3.2.1] OAuth bypass default opt-in (false).
* [3.2.0] Patch scalar-safe untuk input array, izinkan ‘register’ jika aktif.
*/
public function login_stealth_mode()
{
if (is_user_logged_in()) return;

$action = “”;
if (isset($_REQUEST[“action”]) && is_string($_REQUEST[“action”])) {
$action = sanitize_key(wp_unslash($_REQUEST[“action”]));
}

$allowed_actions = [“logout”, “lostpassword”, “rp”, “resetpass”, “postpass”];
if (get_option(“users_can_register”)) {
$allowed_actions[] = “register”;
}
if (in_array($action, $allowed_actions, true) || isset($_REQUEST[“interim-login”])) {
return;
}

// [3.2.1] OAuth/Social Login bypass sekarang default opt-in (false).
$allow_oauth_bypass = (bool) apply_filters(‘wuss_login_stealth_allow_oauth_callback_bypass’, false);

if ($allow_oauth_bypass) {
$social_keys = apply_filters(
“wuss_login_stealth_allowed_params”,
[
“loginSocial”,
“hauth_done”,
“hauth.done”,
“wsl_process”,
“loginGoogle”,
“loginFacebook”,
“loginTwitter”,
“loginYahoo”,
]
);
foreach ($social_keys as $k) {
if (isset($_GET[$k]) || isset($_POST[$k])) {
return;
}
}

$has_code = isset($_GET[“code”]) || isset($_POST[“code”]);
$has_state = isset($_GET[“state”]) || isset($_POST[“state”]);
if ($has_code && $has_state) {
return;
}

$has_oauth_token = isset($_GET[“oauth_token”]) || isset($_POST[“oauth_token”]);
$has_oauth_verifier = isset($_GET[“oauth_verifier”]) || isset($_POST[“oauth_verifier”]);
if ($has_oauth_token && $has_oauth_verifier) {
return;
}

$provider = “”;
if (isset($_GET[“provider”]) && is_string($_GET[“provider”])) {
$provider = strtolower(sanitize_key(wp_unslash($_GET[“provider”])));
} elseif (isset($_POST[“provider”]) && is_string($_POST[“provider”])) {
$provider = strtolower(sanitize_key(wp_unslash($_POST[“provider”])));
}
$allowed_providers = apply_filters(
“wuss_login_stealth_allowed_oauth_providers”,
[“google”, “yahoo”, “facebook”, “twitter”]
);
$has_token_like = $has_code || $has_oauth_token
|| isset($_GET[“access_token”]) || isset($_POST[“access_token”])
|| isset($_GET[“id_token”]) || isset($_POST[“id_token”]);
if ($provider && in_array($provider, $allowed_providers, true) && ($has_state || $has_token_like)) {
return;
}
}

if ($this->has_valid_admin_flow_cookie()) {
return;
}

if (!empty($this->options[“test_mode”])) {
$this->log_security_event(“test_mode_stealth”, $this->get_user_ip(), “Would 404 login”);
return;
}

status_header(404);
nocache_headers();
wp_die(“Not Found”, “404 Not Found”, [“response” => 404]);
}

public function filter_rest_endpoints_users($e) { if (is_user_logged_in()) return $e; unset($e[“/wp/v2/users”], $e[“/wp/v2/users/(?P<id>[\d]+)”]); return $e; }

/**
* [3.2.1] Restore theme 404 template fallback + wp_die() backup.
*/
public function block_author_archives() {
if (is_admin() || !is_author()) return;
status_header(404);
nocache_headers();

$template = get_404_template();
if ($template) {
include $template;
exit;
}

wp_die(
esc_html__(“Not Found”, “wp-ultimate-ssl-security”),
esc_html__(“404 Not Found”, “wp-ultimate-ssl-security”),
[“response” => 404]
);
}

/* ==========================================================================
REST API & AUTH
========================================================================== */

private function get_cached_rest_path() {
if ($this->rest_path !== null) return $this->rest_path;
$path = parse_url(rest_url(), PHP_URL_PATH);
$this->rest_path = is_string($path) ? rtrim($path, “/”) : “”;
return $this->rest_path;
}

private function rest_path_matches($request_path, $allowed_prefix) { $request_path = trailingslashit($request_path); $allowed_prefix = trailingslashit($allowed_prefix); return strpos($request_path, $allowed_prefix) === 0; }

public function restrict_rest_global_for_guests($res)
{
if (empty($this->options[“disable_rest_api”]) || is_user_logged_in()) return $res;
if (!empty($this->options[“test_mode”])) { $this->log_security_event(“test_mode_rest”, $this->get_user_ip(), “Would restrict REST”); return $res; }

// [3.2.2] Filter ini idealnya hanya memblokir request REST asli.
// Guard ini mencegah overblocking pada konfigurasi rest_url/path anomali.
if (!(defined(“REST_REQUEST”) && REST_REQUEST)) {
return $res;
}

$rp = parse_url($_SERVER[“REQUEST_URI”] ?? “”, PHP_URL_PATH);
$rp = is_string($rp) ? $rp : “”;
$rest_base = $this->get_cached_rest_path();

if ($rest_base === “”) {
return $res;
}

if (strpos($rp, $rest_base) !== 0) return $res;
$rel = substr($rp, strlen($rest_base));
$allowed = apply_filters(“wuss_allowed_rest_endpoints”, [“/wp/v2/posts”,”/wp/v2/categories”,”/wp/v2/tags”,”/wp/v2/pages”,”/wp/v2/media”,”/wp/v2/types”,”/wp/v2/statuses”,”/wp/v2/taxonomies”,”/oauth”,”/jwt-auth”,”/wp/v2/search”]);
foreach ($allowed as $ep) { if ($this->rest_path_matches($rel, $ep)) return $res; }
return new WP_Error(“rest_forbidden”, “REST API restricted for security reasons.”, [“status” => 403]);
}

public function limit_rest_per_page($p) { if (isset($p[“per_page”])) { $p[“per_page”][“maximum”] = 50; $p[“per_page”][“default”] = min(10, $p[“per_page”][“default”] ?? 10); } return $p; }

public function set_admin_expiration($e, $uid, $rem)
{
if (!empty($this->options[“auto_logout_admin”])) {
$apply_timeout = apply_filters(
“wuss_apply_session_timeout”,
user_can($uid, “manage_options”),
$uid,
$rem
);
if ($apply_timeout) {
$duration = absint($this->options[“admin_session_duration”]) ?: 14400;
return apply_filters(“wuss_admin_session_duration”, $duration, $uid, $rem);
}
}
return $e;
}

/* ==========================================================================
BRUTE FORCE PROTECTION
========================================================================== */

private function get_parsed_whitelist()
{
if ($this->parsed_whitelist !== null) return $this->parsed_whitelist;
$this->parsed_whitelist = [“ips” => [], “cidrs” => []];
if (empty($this->options[“bf_whitelist”])) return $this->parsed_whitelist;

foreach (explode(“\n”, $this->options[“bf_whitelist”]) as $entry) {
$entry = trim($entry);
if (empty($entry)) continue;

if (strpos($entry, “/”) !== false) {
// [Final] Support IPv4 & IPv6 CIDR natively
$this->parsed_whitelist[“cidrs”][] = $entry;
} elseif (filter_var($entry, FILTER_VALIDATE_IP)) {
$this->parsed_whitelist[“ips”][] = $entry;
}
}
return $this->parsed_whitelist;
}

private function is_ip_whitelisted($ip)
{
if (empty($this->options[“bf_whitelist”])) return false;
$wl = $this->get_parsed_whitelist();
if (in_array($ip, $wl[“ips”], true)) return true;

foreach ($wl[“cidrs”] as $cidr) {
if ($this->ip_matches_cidr($ip, $cidr)) return true;
}
return false;
}

public function check_brute_force_block($user, $username, $password)
{
$ip = $this->get_user_ip(); if ($this->is_ip_whitelisted($ip)) return $user;
if ($this->cache_get($this->generate_cache_key(“lockout”, $ip))) {
if (!empty($this->options[“test_mode”])) { $this->log_security_event(“test_mode_bf_lockout”, $ip, “Username tried: $username”); return $user; }
$this->log_security_event(“lockout_blocked”, $ip, “Username tried: $username”);
return new WP_Error(“too_many_retries”, sprintf(__(“IP Address blocked for %d minutes due to too many failed login attempts.”, “wp-ultimate-ssl-security”), absint($this->options[“bf_lockout_duration”])));
}
return $user;
}

public function log_failed_attempt($username)
{
if (!empty($this->options[“test_mode”])) {
$this->log_security_event(“test_mode_failed_login”, $this->get_user_ip(), “Username: ” . $username);
return;
}

$ip = $this->get_user_ip();
if ($this->is_ip_whitelisted($ip)) return;

if ($this->cache_get($this->generate_cache_key(“lockout”, $ip))) {
return;
}

$max = absint($this->options[“bf_max_attempts”]);
$base_dur = absint($this->options[“bf_lockout_duration”]) * MINUTE_IN_SECONDS;
$counter_ttl = 2 * HOUR_IN_SECONDS;

$ip_key = $this->generate_cache_key(“bf_ip”, $ip);

$count = $this->cache_incr_with_ttl($ip_key, $counter_ttl);

$this->log_security_event(“failed_login”, $ip, “Username: ” . $username . “, IP Attempts: ” . (int)$count);

if ($count >= $max) {
$this->cache_set($this->generate_cache_key(“lockout”, $ip), time(), $base_dur);
$this->log_security_event(“lockout_triggered”, $ip, “Total attempts: {$count}, Lockout: {$base_dur}s”);
}
}

public function reset_login_attempts($ul, $u)
{
$ip = $this->get_user_ip();
if (!$ip || $ip === “0.0.0.0”) return;

// [3.2.2] Jangan hapus global IP counter saat login sukses.
// Jika dihapus, attacker dengan akun valid dapat “mencuci” counter
// brute-force dari IP yang sama.
$this->log_security_event(“login_success”, $ip, “User: ” . $this->sanitize_log_text($ul));
}

/* ==========================================================================
.HTACCESS MANAGEMENT
========================================================================== */

/**
* [3.2.1] No-op jika tidak ada fitur .htaccess aktif (mencegah error di read-only hosting).
*/
private function update_htaccess($opt)
{
if (!function_exists(“insert_with_markers”)) require_once ABSPATH . “wp-admin/includes/misc.php”;
if (!function_exists(“get_home_path”)) require_once ABSPATH . “wp-admin/includes/file.php”;

$hp = get_home_path();
$hf = $hp . “.htaccess”;

$needs_htaccess =
!empty($opt[“block_bad_queries_htaccess”]) ||
!empty($opt[“force_ssl_htaccess”]) ||
!empty($opt[“block_sensitive_files”]) ||
!empty($opt[“disable_xmlrpc”]) ||
!empty($opt[“disable_directory_browsing”]);

if (!$needs_htaccess) {
if (!file_exists($hf)) {
return true;
}

$has_marker = false;
if (is_readable($hf)) {
$content = file_get_contents($hf);
$has_marker = is_string($content) && strpos($content, “WP Ultimate SSL Security”) !== false;
}

if (!$has_marker) {
return true;
}

if (!is_writable($hf)) {
return new WP_Error(
“htaccess_permission”,
__(“Cannot remove existing WUSS .htaccess rules because .htaccess is not writable.”, “wp-ultimate-ssl-security”)
);
}

return insert_with_markers($hf, “WP Ultimate SSL Security”, [])
? true
: new WP_Error(“htaccess_write_failed”, __(“Failed to remove WUSS .htaccess rules.”, “wp-ultimate-ssl-security”));
}

if (!file_exists($hf)) {
if (!is_writable($hp) || !touch($hf)) {
return new WP_Error(“htaccess_permission”, __(“Cannot create .htaccess file (home directory not writable).”, “wp-ultimate-ssl-security”));
}
}
if (!is_writable($hf)) {
return new WP_Error(“htaccess_permission”, __(“.htaccess file is not writable.”, “wp-ultimate-ssl-security”));
}

$backup = $hp . “.htaccess.wuss-backup”;
if (file_exists($hf) && !file_exists($backup) && is_readable($hf)) {
if (!copy($hf, $backup)) {
$this->log_security_event(“htaccess_backup_failed”, $this->get_user_ip(), $backup);
}
}

$r = [“<IfModule mod_rewrite.c>”, “RewriteEngine On”];
if (!empty($opt[“block_bad_queries_htaccess”])) { $r[] = “# Block Bad Queries”; $r[] = ‘RewriteCond %{QUERY_STRING} (eval\(|\$_REQUEST\[|\$_GET\[|\$_POST\[|\$_COOKIE\[) [NC,OR]’; $r[] = “RewriteCond %{QUERY_STRING} (<script) [NC]”; $r[] = “RewriteRule .* – [F,L]”; }

if (!empty($opt[“force_ssl_htaccess”])) {
// [3.2.2] Secure by default:
// .htaccess tidak bisa membedakan trusted vs spoofed X-Forwarded-Proto
// jika origin server dapat diakses langsung. Karena itu kombinasi ini
// ditolak kecuali sysadmin explicit opt-in via filter.
if (!empty($opt[“enable_proxy_trust”])) {
$allow_proxy_htaccess_ssl = (bool) apply_filters(
“wuss_allow_htaccess_ssl_with_proxy_trust”,
false
);

if (!$allow_proxy_htaccess_ssl) {
return new WP_Error(
“unsafe_htaccess_proxy_ssl”,
__(
“.htaccess Force SSL tidak dijalankan bersama Trust Proxy karena dapat dibypass jika origin server dapat diakses langsung. Gunakan Force SSL PHP atau batasi origin via firewall ke IP proxy/CDN. Override via filter wuss_allow_htaccess_ssl_with_proxy_trust hanya jika Anda memahami risikonya.”,
“wp-ultimate-ssl-security”
)
);
}
}

$r[] = “# Force SSL”;
$r[] = “RewriteCond %{HTTPS} !=on”;
if (!empty($opt[“enable_proxy_trust”])) {
$r[] = “RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]”;
}
$canonical_host = parse_url(home_url(), PHP_URL_HOST);
if ($canonical_host && preg_match(‘/^[a-z0-9.-]+$/i’, $canonical_host)) {
$r[] = ‘RewriteRule ^ https://’ . $canonical_host . ‘%{REQUEST_URI} [L,R=301,NE]’;
} else {
return new WP_Error(
“invalid_canonical_host”,
__(“Cannot determine a safe canonical host for .htaccess SSL redirect. Force SSL .htaccess dilewati; perbaiki konfigurasi siteurl/home atau gunakan Force SSL PHP.”, “wp-ultimate-ssl-security”)
);
}
}

$r[] = “</IfModule>”;
if (!empty($opt[“block_sensitive_files”])) { foreach([“wp-config.php”,”readme.html”,”license.txt”,”.htpasswd”,”.htaccess.bak”] as $f) { $r[] = ‘<Files “‘ . $f . ‘”>’; $r[] = “Require all denied”; $r[] = “</Files>”; } }
if (!empty($opt[“disable_xmlrpc”])) { $r[] = ‘<Files “xmlrpc.php”>’; $r[] = “Require all denied”; $r[] = “</Files>”; }
if (!empty($opt[“disable_directory_browsing”])) $r[] = “Options -Indexes”;

return insert_with_markers($hf, “WP Ultimate SSL Security”, $r) ? true : new WP_Error(“htaccess_write_failed”, __(“Failed to write to .htaccess file.”, “wp-ultimate-ssl-security”));
}

private function secure_uploads_directory($enable)
{
if (!function_exists(“insert_with_markers”)) {
require_once ABSPATH . “wp-admin/includes/misc.php”;
}

$u = wp_get_upload_dir();

if (empty($u[“basedir”]) || !is_dir($u[“basedir”])) {
return new WP_Error(
“uploads_missing”,
__(“Folder uploads tidak ditemukan.”, “wp-ultimate-ssl-security”)
);
}

$htaccess = trailingslashit($u[“basedir”]) . “.htaccess”;

if ($enable) {
if (!file_exists($htaccess)) {
if (!is_writable($u[“basedir”]) || !touch($htaccess)) {
return new WP_Error(
“uploads_writable”,
__(“Folder uploads tidak writable atau .htaccess tidak bisa dibuat.”, “wp-ultimate-ssl-security”)
);
}
}
if (!is_writable($htaccess)) {
return new WP_Error(
“uploads_htaccess_writable”,
__(“File uploads/.htaccess tidak writable.”, “wp-ultimate-ssl-security”)
);
}
$rules = [
‘<FilesMatch “\.(php|phtml|php[0-9]|phar|phpt|shtml)$”>’,
” Require all denied”,
“</FilesMatch>”,
“RemoveHandler .php .phtml .php3 .php4 .php5 .php7 .php8 .phar .phpt .shtml”,
“RemoveType .php .phtml .php3 .php4 .php5 .php7 .php8 .phar .phpt .shtml”,
];
} else {
if (!file_exists($htaccess)) {
return true;
}
if (!is_writable($htaccess)) {
return new WP_Error(
“uploads_htaccess_writable”,
__(“File uploads/.htaccess tidak writable untuk menghapus marker.”, “wp-ultimate-ssl-security”)
);
}
$rules = [];
}

return insert_with_markers($htaccess, “WP Ultimate Uploads Security”, $rules)
? true
: new WP_Error(
“write_failed”,
__(“Gagal menulis file .htaccess ke folder uploads.”, “wp-ultimate-ssl-security”)
);
}

/* ==========================================================================
ADMIN UI
========================================================================== */

public function add_plugin_page() { add_options_page(__(“Ultimate SSL & Security”, “wp-ultimate-ssl-security”), __(“Ultimate Security”, “wp-ultimate-ssl-security”), “manage_options”, “wp-ultimate-ssl-security”, [$this, “create_admin_page”]); }
private function is_feature_active($k) { $a=false; $s=””; if(isset($this->options[$k])&&(int)$this->options[$k]===1){$a=true;$s=”plugin”;} if($k===”disable_file_edit”&&defined(“DISALLOW_FILE_EDIT”)&&DISALLOW_FILE_EDIT){$a=true;$s=”external”;} if($k===”disable_file_mods”&&defined(“DISALLOW_FILE_MODS”)&&DISALLOW_FILE_MODS){$a=true;$s=”external”;} if($k===”enable_ssl”&&(is_ssl()||strpos(get_option(“siteurl”),”https”)!==false)){$a=true;$s=”external”;} return [“active”=>$a,”source”=>$s]; }

public function init_admin_save() { if(isset($_POST[“wuss_save_config”])) { if(!current_user_can(“manage_options”)||!isset($_POST[“wuss_nonce”])||!wp_verify_nonce($_POST[“wuss_nonce”],”wuss_settings_action”)) wp_die(esc_html__(“Security check failed.”,”wp-ultimate-ssl-security”)); $this->handle_manual_save(); } }

private function maybe_flush_rewrite_rules_on_enum_change($old, $new)
{
$old_state = !empty($old[“block_user_enum_strict”]);
$new_state = !empty($new[“block_user_enum_strict”]);

if ($old_state !== $new_state) {
flush_rewrite_rules(false);
}
}

private function handle_manual_save()
{
$old = $this->get_options();
$s = $this->sanitize($_POST[“wuss_settings”] ?? []);
$has_errors = false;

$err = $this->update_htaccess($s);
if (is_wp_error($err)) {
$has_errors = true;
add_settings_error(“wuss_notices”, “htaccess_error”, $err->get_error_message(), “error”);
foreach ([“block_bad_queries_htaccess”, “force_ssl_htaccess”, “block_sensitive_files”, “disable_directory_browsing”] as $k) {
$s[$k] = !empty($old[$k]) ? 1 : 0;
}
}

$err2 = $this->secure_uploads_directory(!empty($s[“secure_uploads_htaccess”]));
if (is_wp_error($err2)) {
$has_errors = true;
add_settings_error(“wuss_notices”, “uploads_error”, $err2->get_error_message(), “error”);
$s[“secure_uploads_htaccess”] = !empty($old[“secure_uploads_htaccess”]) ? 1 : 0;
}

update_option($this->option_name, $s);
$this->options = $s;

if (!empty($s[“enable_ssl”])) $this->enable_ssl_urls();

$this->bump_csp_cache_version();
$this->maybe_flush_rewrite_rules_on_enum_change($old, $s);
$this->parsed_whitelist = null;

if (!$has_errors) {
add_settings_error(“wuss_notices”, “settings_saved”, __(“Settings & Security Rules updated successfully.”, “wp-ultimate-ssl-security”), “updated”);
} else {
add_settings_error(“wuss_notices”, “settings_partial”, __(“Settings saved to database, but some file system rules failed to apply. Please check the errors above.”, “wp-ultimate-ssl-security”), “warning”);
}

set_transient(“settings_errors”, get_settings_errors(), 30);
wp_safe_redirect(admin_url(“options-general.php?page=wp-ultimate-ssl-security&settings-updated=1”)); exit;
}

public function sanitize($i)
{
if(!is_array($i)) return []; $s = [];
foreach([“enable_ssl”,”force_ssl”,”mixed_content_fixer”,”disable_xmlrpc”,”hide_version”,”block_user_enum”,”disable_rest_api”,”auto_logout_admin”,”clean_head_meta”,”enable_brute_force”,”force_ssl_htaccess”,”security_headers”,”block_sensitive_files”,”disable_file_edit”,”disable_file_mods”,”optimize_heartbeat”,”disable_directory_browsing”,”block_bad_queries_htaccess”,”enable_rate_limit”,”block_public_feeds”,”hsts_preload”,”hsts_include_subdomains”,”enable_logging”,”enable_proxy_trust”,”enable_login_stealth”,”block_user_enum_strict”,”obfuscate_login_errors”,”disable_app_passwords”,”secure_uploads_htaccess”,”remove_query_strings”,”test_mode”] as $k) $s[$k] = isset($i[$k]) ? 1 : 0;
$s[“rate_limit_max”] = isset($i[“rate_limit_max”]) ? max(10,min(1000,absint($i[“rate_limit_max”]))) : 180;
$s[“rate_limit_seconds”] = isset($i[“rate_limit_seconds”]) ? max(10,min(3600,absint($i[“rate_limit_seconds”]))) : 60;
$s[“heartbeat_frontend”] = isset($i[“heartbeat_frontend”]) ? max(15,min(300,absint($i[“heartbeat_frontend”]))) : 60;
$s[“heartbeat_backend”] = isset($i[“heartbeat_backend”]) ? max(15,min(120,absint($i[“heartbeat_backend”]))) : 30;
$s[“bf_max_attempts”] = isset($i[“bf_max_attempts”]) ? max(3,min(20,absint($i[“bf_max_attempts”]))) : 10;
$s[“bf_lockout_duration”] = isset($i[“bf_lockout_duration”]) ? max(5,min(1440,absint($i[“bf_lockout_duration”]))) : 60;
if (isset($i[“csp_custom_exceptions”])) {
$raw_csp = wp_unslash($i[“csp_custom_exceptions”]);
$raw_csp = str_replace([“\r\n”, “\r”], “\n”, $raw_csp);
$raw_csp = preg_replace(‘/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/’, ”, $raw_csp);
$raw_csp = substr($raw_csp, 0, 8192);
$s[“csp_custom_exceptions”] = trim($raw_csp);
} else {
$s[“csp_custom_exceptions”] = “”;
}
$valid_csp_modes = [‘strict’, ‘compatible’, ‘report_only’];
$s[“csp_mode”] = isset($i[“csp_mode”]) && in_array($i[“csp_mode”], $valid_csp_modes, true)
? $i[“csp_mode”]
: “strict”;
$s[“admin_session_duration”] = isset($i[“admin_session_duration”]) ? max(1800,min(86400,absint($i[“admin_session_duration”]))) : 14400;
if(isset($i[“bf_whitelist”])) { $ips = array_filter(array_map(“trim”, explode(“\n”, sanitize_textarea_field(wp_unslash($i[“bf_whitelist”])))), function($ip){ return filter_var($ip, FILTER_VALIDATE_IP) || (strpos($ip, “/”) !== false && count(explode(“/”, $ip)) === 2); }); $s[“bf_whitelist”] = implode(“\n”, $ips); } else $s[“bf_whitelist”] = “”;
return $s;
}

public function admin_notices()
{
settings_errors(“wuss_notices”);
if (!empty($this->options[“force_ssl”]) && !$this->is_ssl_possible()) echo ‘<div class=”notice notice-warning”><p>⚠️ ‘ . esc_html__(“SSL Force diaktifkan tetapi server tidak terdeteksi mendukung HTTPS.”, “wp-ultimate-ssl-security”) . ‘</p></div>’;
if (!empty($this->options[“test_mode”])) echo ‘<div class=”notice notice-warning”><p>🧪 <strong>’ . esc_html__(“TEST MODE AKTIF:”, “wp-ultimate-ssl-security”) . ‘</strong> ‘ . esc_html__(“Semua fitur keamanan hanya mencatat log, TIDAK memblokir traffic & TIDAK mengubah state counter.”, “wp-ultimate-ssl-security”) . ‘</p></div>’;

$raw_opts = get_option($this->option_name, []);
if (!$this->use_object_cache && (!empty($raw_opts[“enable_rate_limit”]) || !empty($raw_opts[“enable_brute_force”]))) {
echo ‘<div class=”notice notice-error”><p><strong>’ . esc_html__(“WUSS DISABLED:”, “wp-ultimate-ssl-security”) . ‘</strong> ‘;
echo esc_html__(“Rate Limiting dan Brute Force Protection dipaksa non-aktif karena tidak ada persistent object cache (Redis/Memcached).”, “wp-ultimate-ssl-security”);
echo ‘</p></div>’;
}

if (!empty($raw_opts[“force_ssl_htaccess”]) && !empty($raw_opts[“enable_proxy_trust”])) {
echo ‘<div class=”notice notice-warning”><p>⚠️ <strong>’ . esc_html__(“.htaccess Force SSL + Trust Proxy:”, “wp-ultimate-ssl-security”) . ‘</strong> ‘;
echo esc_html__(“.htaccess Force SSL dengan Trust Proxy Headers hanya aman jika origin server tidak bisa diakses langsung dari internet. Jika origin terbuka, gunakan Force SSL PHP atau batasi firewall origin ke IP proxy/CDN.”, “wp-ultimate-ssl-security”);
echo ‘</p></div>’;
}

if (!empty($this->options[“remove_query_strings”])) echo ‘<div class=”notice notice-warning”><p>⚠️ <strong>’ . esc_html__(“Remove Query Strings:”, “wp-ultimate-ssl-security”) . ‘</strong> ‘ . esc_html__(“Fitur ini dapat menyebabkan cache busting tidak berfungsi pada CDN.”, “wp-ultimate-ssl-security”) . ‘</p></div>’;
if (!empty($this->options[“hsts_preload”]) && empty($this->options[“hsts_include_subdomains”])) echo ‘<div class=”notice notice-warning”><p>⚠️ ‘ . esc_html__(“HSTS Preload akan otomatis mengaktifkan includeSubDomains.”, “wp-ultimate-ssl-security”) . ‘</p></div>’;
}

public function create_admin_page()
{
if (!current_user_can(“manage_options”)) return;
$options = $this->options;
$status_edit = $this->is_feature_active(“disable_file_edit”);
$status_mods = $this->is_feature_active(“disable_file_mods”);
$status_ssl = $this->is_feature_active(“enable_ssl”);
?>
<div class=”wrap”>
<h1><?php esc_html_e(“WP Ultimate SSL & Security”, “wp-ultimate-ssl-security”); ?> <span style=”font-size:0.5em;font-weight:normal;color:#666;”>v<?php echo esc_html(WUSS_VERSION); ?> (Hardened)</span></h1>
<?php if ($this->use_object_cache): ?><div class=”notice notice-info” style=”margin-bottom:20px;”><p>⚡ <strong><?php esc_html_e(“Performance Mode:”, “wp-ultimate-ssl-security”); ?></strong> <?php esc_html_e(“Object Cache detected. High-performance locking active.”, “wp-ultimate-ssl-security”); ?></p></div><?php endif; ?>
<form method=”post” action=””>
<?php wp_nonce_field(“wuss_settings_action”, “wuss_nonce”); ?>
<h2 class=”title”><?php esc_html_e(“Global Settings”, “wp-ultimate-ssl-security”); ?></h2>
<table class=”form-table”>
<tr><th scope=”row”><?php esc_html_e(“Test Mode”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[test_mode]” value=”1″ <?php checked(!empty($options[“test_mode”])); ?> /> <strong style=”color:#d63638;”>🧪 <?php esc_html_e(“AKTIFKAN TEST MODE”, “wp-ultimate-ssl-security”); ?></strong></label><p class=”description”><?php esc_html_e(“Catat event keamanan tapi JANGAN blokir & JANGAN ubah state counter.”, “wp-ultimate-ssl-security”); ?></p></td></tr>
</table>
<h2 class=”title”><?php esc_html_e(“Server-Side Protection”, “wp-ultimate-ssl-security”); ?></h2>
<table class=”form-table”>
<tr><th scope=”row”><?php esc_html_e(“.htaccess Query Firewall”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[block_bad_queries_htaccess]” value=”1″ <?php checked(!empty($options[“block_bad_queries_htaccess”])); ?> /> <strong><?php esc_html_e(“Aktifkan Firewall”, “wp-ultimate-ssl-security”); ?></strong></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Rate Limiting”, “wp-ultimate-ssl-security”); ?></th><td>
<?php if (!$this->use_object_cache): ?>
<p class=”description” style=”color:#d63638;”><em><?php esc_html_e(“Disabled: Requires persistent Object Cache (Redis).”, “wp-ultimate-ssl-security”); ?></em></p>
<?php else: ?>
<label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[enable_rate_limit]” value=”1″ <?php checked(!empty($options[“enable_rate_limit”])); ?> /> <strong><?php esc_html_e(“Aktifkan Rate Limiting (Global Per-IP)”, “wp-ultimate-ssl-security”); ?></strong></label>
<?php if (!empty($options[“enable_rate_limit”])): ?>
<table class=”form-table” style=”background:#f0f6ff;border:1px solid #c3c4c7;margin-top:10px;padding:10px;width:auto;”>
<tr><th style=”width:150px”><?php esc_html_e(“Max Requests”, “wp-ultimate-ssl-security”); ?></th><td><input type=”number” name=”<?php echo esc_attr($this->option_name); ?>[rate_limit_max]” value=”<?php echo esc_attr($options[“rate_limit_max”]); ?>” min=”10″ max=”1000″ class=”small-text” /></td></tr>
<tr><th><?php esc_html_e(“Window (Detik)”, “wp-ultimate-ssl-security”); ?></th><td><input type=”number” name=”<?php echo esc_attr($this->option_name); ?>[rate_limit_seconds]” value=”<?php echo esc_attr($options[“rate_limit_seconds”]); ?>” min=”10″ max=”3600″ class=”small-text” /></td></tr>
</table>
<?php endif; ?>
<?php endif; ?>
</td></tr>
<tr><th scope=”row”><?php esc_html_e(“Trust Proxy Headers”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[enable_proxy_trust]” value=”1″ <?php checked(!empty($options[“enable_proxy_trust”])); ?> /> <strong><?php esc_html_e(“Percaya header proxy (CloudFlare & trusted proxies)”, “wp-ultimate-ssl-security”); ?></strong></label><p class=”description” style=”color:#d63638;”>⚠️ <strong><?php esc_html_e(“Hanya aktifkan”, “wp-ultimate-ssl-security”); ?></strong> <?php esc_html_e(“jika origin server diblokir dari internet langsung.”, “wp-ultimate-ssl-security”); ?></p></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Block Public Feeds”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[block_public_feeds]” value=”1″ <?php checked(!empty($options[“block_public_feeds”])); ?> /> <strong><?php esc_html_e(“Matikan RSS Feeds”, “wp-ultimate-ssl-security”); ?></strong></label></td></tr>
</table>
<h2 class=”title”><?php esc_html_e(“SSL Settings”, “wp-ultimate-ssl-security”); ?></h2>
<table class=”form-table”>
<tr><th scope=”row”><?php esc_html_e(“Enable SSL”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[enable_ssl]” value=”1″ <?php checked($status_ssl[“active”]); ?> <?php disabled($status_ssl[“source”]==”external”); ?> /> <?php esc_html_e(“Ubah URL ke HTTPS”, “wp-ultimate-ssl-security”); ?></label><?php if($status_ssl[“source”]==”external”): ?><p class=”description”><?php esc_html_e(“Status: Active (via external)”, “wp-ultimate-ssl-security”); ?></p><?php endif; ?></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Force SSL (PHP)”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[force_ssl]” value=”1″ <?php checked(!empty($options[“force_ssl”])); ?> /> <?php esc_html_e(“Redirect HTTP ke HTTPS via PHP (proxy-aware)”, “wp-ultimate-ssl-security”); ?></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Force SSL (.htaccess)”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[force_ssl_htaccess]” value=”1″ <?php checked(!empty($options[“force_ssl_htaccess”])); ?> /> <?php esc_html_e(“Redirect via .htaccess (direct connection only)”, “wp-ultimate-ssl-security”); ?></label><p class=”description” style=”color:#d63638;”>⚠️ <?php esc_html_e(“Hanya untuk origin yang tidak di-reverse-proxy. Jangan aktifkan bersama Trust Proxy.”, “wp-ultimate-ssl-security”); ?></p></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Mixed Content Mitigation via CSP”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[mixed_content_fixer]” value=”1″ <?php checked(!empty($options[“mixed_content_fixer”])); ?> /> <?php esc_html_e(“Tambahkan directive upgrade-insecure-requests ke CSP”, “wp-ultimate-ssl-security”); ?></label><p class=”description”><?php esc_html_e(“Tidak melakukan rewrite URL HTTP->HTTPS, hanya memberi sinyal ke browser modern untuk auto-upgrade.”, “wp-ultimate-ssl-security”); ?></p></td></tr>
</table>
<h2 class=”title”><?php esc_html_e(“WordPress Hardening”, “wp-ultimate-ssl-security”); ?></h2>
<table class=”form-table”>
<tr><th scope=”row”><?php esc_html_e(“Heartbeat API”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[optimize_heartbeat]” value=”1″ <?php checked(!empty($options[“optimize_heartbeat”])); ?> /> <strong><?php esc_html_e(“Kontrol Heartbeat”, “wp-ultimate-ssl-security”); ?></strong></label><?php if(!empty($options[“optimize_heartbeat”])): ?><table class=”form-table” style=”background:#f9f9f9;border:1px solid #ddd;margin-top:10px;padding:10px;width:auto;”><tr><th style=”width:150px”><?php esc_html_e(“Frontend”, “wp-ultimate-ssl-security”); ?></th><td><input type=”number” name=”<?php echo esc_attr($this->option_name); ?>[heartbeat_frontend]” value=”<?php echo esc_attr($options[“heartbeat_frontend”]); ?>” min=”15″ max=”300″ class=”small-text” />s</td></tr><tr><th><?php esc_html_e(“Backend”, “wp-ultimate-ssl-security”); ?></th><td><input type=”number” name=”<?php echo esc_attr($this->option_name); ?>[heartbeat_backend]” value=”<?php echo esc_attr($options[“heartbeat_backend”]); ?>” min=”15″ max=”120″ class=”small-text” />s</td></tr></table><?php endif; ?></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Disable XML-RPC”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[disable_xmlrpc]” value=”1″ <?php checked(!empty($options[“disable_xmlrpc”])); ?> /></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Hide WP Version”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[hide_version]” value=”1″ <?php checked(!empty($options[“hide_version”])); ?> /></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Block ?author=”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[block_user_enum]” value=”1″ <?php checked(!empty($options[“block_user_enum”])); ?> /></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Restrict REST API”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[disable_rest_api]” value=”1″ <?php checked(!empty($options[“disable_rest_api”])); ?> /> <?php esc_html_e(“Batasi untuk publik”, “wp-ultimate-ssl-security”); ?></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Disable File Editor”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[disable_file_edit]” value=”1″ <?php checked($status_edit[“active”]); ?> <?php disabled($status_edit[“source”]==”external”); ?> /></label><?php if($status_edit[“source”]==”external”): ?><p class=”description”><?php esc_html_e(“Active (via wp-config.php)”, “wp-ultimate-ssl-security”); ?></p><?php endif; ?></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Disable File Mods”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[disable_file_mods]” value=”1″ <?php checked($status_mods[“active”]); ?> <?php disabled($status_mods[“source”]==”external”); ?> /></label><?php if($status_mods[“source”]==”external”): ?><p class=”description”><?php esc_html_e(“Active (via wp-config.php)”, “wp-ultimate-ssl-security”); ?></p><?php endif; ?></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Admin Session Timeout”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[auto_logout_admin]” value=”1″ <?php checked(!empty($options[“auto_logout_admin”])); ?> /> <strong><?php esc_html_e(“Aktifkan”, “wp-ultimate-ssl-security”); ?></strong></label><?php if(!empty($options[“auto_logout_admin”])): ?><table class=”form-table” style=”background:#f9f9f9;border:1px solid #ddd;margin-top:10px;padding:10px;width:auto;”><tr><th style=”width:150px”><?php esc_html_e(“Durasi (Detik)”, “wp-ultimate-ssl-security”); ?></th><td><input type=”number” name=”<?php echo esc_attr($this->option_name); ?>[admin_session_duration]” value=”<?php echo esc_attr($options[“admin_session_duration”]); ?>” min=”1800″ max=”86400″ class=”small-text” /></td></tr></table><p class=”description”><?php esc_html_e(“Default: hanya role manage_options. Gunakan filter wuss_apply_session_timeout untuk extend.”, “wp-ultimate-ssl-security”); ?></p><?php endif; ?></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Clean Head Meta”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[clean_head_meta]” value=”1″ <?php checked(!empty($options[“clean_head_meta”])); ?> /></label></td></tr>
</table>
<h2 class=”title”><?php esc_html_e(“Brute Force Protection”, “wp-ultimate-ssl-security”); ?></h2>
<table class=”form-table”>
<tr><th scope=”row”><?php esc_html_e(“Enable Protection”, “wp-ultimate-ssl-security”); ?></th><td>
<?php if (!$this->use_object_cache): ?>
<p class=”description” style=”color:#d63638;”><em><?php esc_html_e(“Disabled: Requires persistent Object Cache (Redis).”, “wp-ultimate-ssl-security”); ?></em></p>
<?php else: ?>
<label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[enable_brute_force]” value=”1″ <?php checked(!empty($options[“enable_brute_force”])); ?> /> <?php esc_html_e(“IP-based tracking (Atomic Increment)”, “wp-ultimate-ssl-security”); ?></label>
<?php endif; ?>
</td></tr>
<tr><th scope=”row”><?php esc_html_e(“Max Attempts”, “wp-ultimate-ssl-security”); ?></th><td><input type=”number” name=”<?php echo esc_attr($this->option_name); ?>[bf_max_attempts]” value=”<?php echo esc_attr($options[“bf_max_attempts”]); ?>” min=”3″ max=”20″ class=”small-text” /></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Lockout (Min)”, “wp-ultimate-ssl-security”); ?></th><td><input type=”number” name=”<?php echo esc_attr($this->option_name); ?>[bf_lockout_duration]” value=”<?php echo esc_attr($options[“bf_lockout_duration”]); ?>” min=”5″ max=”1440″ class=”small-text” /></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Whitelist IP / CIDR”, “wp-ultimate-ssl-security”); ?></th><td><textarea name=”<?php echo esc_attr($this->option_name); ?>[bf_whitelist]” rows=”4″ cols=”40″ class=”large-text code” placeholder=”192.168.1.1&#10;10.0.0.0/24″><?php echo esc_textarea($options[“bf_whitelist”]); ?></textarea><p class=”description”><?php esc_html_e(“Satu IP atau CIDR per baris. Support IPv4 & IPv6.”, “wp-ultimate-ssl-security”); ?></p></td></tr>
</table>
<h2 class=”title”><?php esc_html_e(“Security Headers & CSP”, “wp-ultimate-ssl-security”); ?></h2>
<table class=”form-table”>
<tr><th scope=”row”><?php esc_html_e(“Security Headers”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[security_headers]” value=”1″ <?php checked(!empty($options[“security_headers”])); ?> /> <strong><?php esc_html_e(“Aktifkan”, “wp-ultimate-ssl-security”); ?></strong></label></td></tr>
<?php if(!empty($options[“security_headers”])): ?>
<tr><th scope=”row”><?php esc_html_e(“CSP Mode”, “wp-ultimate-ssl-security”); ?></th><td>
<select name=”<?php echo esc_attr($this->option_name); ?>[csp_mode]”>
<option value=”strict” <?php selected($options[“csp_mode”] ?? “strict”, “strict”); ?>><?php esc_html_e(“Strict Compatible – Enforcement with WordPress-safe inline allowance”, “wp-ultimate-ssl-security”); ?></option>
<option value=”compatible” <?php selected($options[“csp_mode”] ?? “strict”, “compatible”); ?>><?php esc_html_e(“Compatible – Allow common external HTTPS resources (CDN/Ads/GTM)”, “wp-ultimate-ssl-security”); ?></option>
<option value=”report_only” <?php selected($options[“csp_mode”] ?? “strict”, “report_only”); ?>><?php esc_html_e(“Report-Only – Log violations without blocking (debug)”, “wp-ultimate-ssl-security”); ?></option>
</select>
<p class=”description”>
<?php esc_html_e(“Strict Compatible: script-src terbatas ‘self’ + ‘unsafe-inline’ untuk kompatibilitas WordPress; resource eksternal perlu di-whitelist manual di Raw CSP Directives.”, “wp-ultimate-ssl-security”); ?><br>
<?php esc_html_e(“Compatible: script-src membuka https: + blob: agar OneSignal/GTM/AdSense bisa load otomatis.”, “wp-ultimate-ssl-security”); ?><br>
<?php esc_html_e(“Report-Only: header Content-Security-Policy-Report-Only, browser log violation ke console tanpa block. Cocok untuk debugging sebelum enforcement.”, “wp-ultimate-ssl-security”); ?>
</p>
</td></tr>
<tr><th scope=”row”><?php esc_html_e(“HSTS includeSubDomains”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[hsts_include_subdomains]” value=”1″ <?php checked(!empty($options[“hsts_include_subdomains”])); ?> /> <strong><?php esc_html_e(“Aktifkan”, “wp-ultimate-ssl-security”); ?></strong></label> <span style=”color:#d63638″>⚠️ <?php esc_html_e(“Pastikan semua subdomain menggunakan SSL”, “wp-ultimate-ssl-security”); ?></span></td></tr>
<tr><th scope=”row”><?php esc_html_e(“HSTS Preload”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[hsts_preload]” value=”1″ <?php checked(!empty($options[“hsts_preload”])); ?> /> <strong><?php esc_html_e(“Aktifkan”, “wp-ultimate-ssl-security”); ?></strong></label> <span style=”color:#d63638″>⚠️ <?php esc_html_e(“Permanen di browser preload list. Auto-includeSubDomains.”, “wp-ultimate-ssl-security”); ?></span></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Raw CSP Directives”, “wp-ultimate-ssl-security”); ?></th><td><textarea name=”<?php echo esc_attr($this->option_name); ?>[csp_custom_exceptions]” rows=”4″ cols=”60″ class=”large-text code” placeholder=”script-src https://cdn.onesignal.com https://pagead2.googlesyndication.com&#10;frame-src https://googleads.g.doubleclick.net https://*.doubleclick.net&#10;worker-src blob:”><?php echo esc_textarea($options[“csp_custom_exceptions”]); ?></textarea><p class=”description”>
<?php esc_html_e(‘Directive yang diizinkan: script-src, style-src, img-src, connect-src, font-src, frame-src, media-src, worker-src, child-src, form-action. default-src sengaja tidak diizinkan karena terlalu luas (bisa di-enable via filter wuss_allowed_csp_custom_directives). Source akan di-merge ke directive existing (bukan duplicate directive). Titik koma (;) sebelum nama directive diizinkan sebagai pemisah (mis. script-src \’unsafe-eval\’; worker-src blob:). Karakter <, >, “, dan ; dilarang di dalam source.’, ‘wp-ultimate-ssl-security’); ?>
</p></td></tr>
<?php endif; ?>
<tr><th scope=”row”><?php esc_html_e(“Block Sensitive Files”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[block_sensitive_files]” value=”1″ <?php checked(!empty($options[“block_sensitive_files”])); ?> /></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Disable Dir Browsing”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[disable_directory_browsing]” value=”1″ <?php checked(!empty($options[“disable_directory_browsing”])); ?> /></label></td></tr>
</table>
<h2 class=”title”><?php esc_html_e(“Advanced & Addons”, “wp-ultimate-ssl-security”); ?></h2>
<table class=”form-table”>
<tr><th scope=”row”><?php esc_html_e(“Security Logging”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[enable_logging]” value=”1″ <?php checked(!empty($options[“enable_logging”])); ?> /></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Remove ?ver=”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[remove_query_strings]” value=”1″ <?php checked(!empty($options[“remove_query_strings”])); ?> /></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Login Stealth”, “wp-ultimate-ssl-security”); ?></th><td>
<label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[enable_login_stealth]” value=”1″ <?php checked(!empty($options[“enable_login_stealth”])); ?> /> <?php esc_html_e(“404 untuk direct hit /wp-login.php”, “wp-ultimate-ssl-security”); ?></label>
<p class=”description” style=”color:#d63638;”>⚠️ <strong><?php esc_html_e(“PERINGATAN KOMPATIBILITAS:”, “wp-ultimate-ssl-security”); ?></strong> <?php esc_html_e(“Mode ini memblokir akses langsung ke /wp-login.php. Selalu gunakan /wp-admin/ untuk login (signed cookie otomatis di-set).”, “wp-ultimate-ssl-security”); ?></p>
<p class=”description”><?php esc_html_e(“Login via /wp-admin/ akan diizinkan melalui signed cookie HMAC 5 menit. Password manager yang pre-fetch /wp-login.php akan di-404.”, “wp-ultimate-ssl-security”); ?></p>
</td></tr>
<tr><th scope=”row”><?php esc_html_e(“Obfuscate Errors”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[obfuscate_login_errors]” value=”1″ <?php checked(!empty($options[“obfuscate_login_errors”])); ?> /></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Strict Anti-Enum”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[block_user_enum_strict]” value=”1″ <?php checked(!empty($options[“block_user_enum_strict”])); ?> /> <?php esc_html_e(“Blokir REST Users & Author Archive”, “wp-ultimate-ssl-security”); ?></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Disable App Passwords”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[disable_app_passwords]” value=”1″ <?php checked(!empty($options[“disable_app_passwords”])); ?> /></label></td></tr>
<tr><th scope=”row”><?php esc_html_e(“Secure Uploads”, “wp-ultimate-ssl-security”); ?></th><td><label><input type=”checkbox” name=”<?php echo esc_attr($this->option_name); ?>[secure_uploads_htaccess]” value=”1″ <?php checked(!empty($options[“secure_uploads_htaccess”])); ?> /> <?php esc_html_e(“Blokir PHP di uploads”, “wp-ultimate-ssl-security”); ?></label></td></tr>
</table>
<?php submit_button(__(“Save All Settings”, “wp-ultimate-ssl-security”), “primary”, “wuss_save_config”); ?>
</form>
</div>
<?php
}

/* ==========================================================================
LIFECYCLE HOOKS
========================================================================== */

public static function activate() {
add_option(“wuss_settings”, self::get_default_options_static(), “”, “no”);
add_option(“wuss_plugin_version”, WUSS_VERSION, “”, “no”);
add_option(“wuss_csp_cache_version”, (string) time(), “”, “no”); // [Final] Pre-seed CSP cache version
flush_rewrite_rules();
}

public static function deactivate() {
if (!function_exists(“insert_with_markers”)) require_once ABSPATH . “wp-admin/includes/misc.php”;
if (!function_exists(“get_home_path”)) require_once ABSPATH . “wp-admin/includes/file.php”;

$hf = get_home_path() . “.htaccess”;
if (file_exists($hf) && is_writable($hf)) {
insert_with_markers($hf, “WP Ultimate SSL Security”, []);
}

$uploads = wp_get_upload_dir();
if (!empty($uploads[“basedir”])) {
$uploads_htaccess = trailingslashit($uploads[“basedir”]) . “.htaccess”;
if (file_exists($uploads_htaccess) && is_writable($uploads_htaccess)) {
insert_with_markers($uploads_htaccess, “WP Ultimate Uploads Security”, []);
}
}

flush_rewrite_rules();
}

public static function uninstall() {
delete_option(“wuss_settings”);
delete_option(“wuss_plugin_version”);
delete_option(“wuss_file_monitor_last”);
delete_option(“wuss_csp_cache_version”);

if (!function_exists(“insert_with_markers”)) require_once ABSPATH . “wp-admin/includes/misc.php”;
if (!function_exists(“get_home_path”)) require_once ABSPATH . “wp-admin/includes/file.php”;

$hf = get_home_path() . “.htaccess”;
if (file_exists($hf) && is_writable($hf)) {
insert_with_markers($hf, “WP Ultimate SSL Security”, []);
}

$uploads = wp_get_upload_dir();
if (!empty($uploads[“basedir”])) {
$uploads_htaccess = trailingslashit($uploads[“basedir”]) . “.htaccess”;
if (file_exists($uploads_htaccess) && is_writable($uploads_htaccess)) {
insert_with_markers($uploads_htaccess, “WP Ultimate Uploads Security”, []);
}
}
}

/* ==========================================================================
WP-CLI HANDLER
========================================================================== */

public function cli_handler($args, $assoc_args)
{
$command = $args[0] ?? “help”;
switch ($command) {
case “status”: \WP_CLI::line(“Version: ” . WUSS_VERSION); \WP_CLI::line(“Active rules: ” . count(array_filter($this->options))); \WP_CLI::line(“Object cache: ” . ($this->use_object_cache ? “Yes (Atomic Increment)” : “No (Rate Limit/BF Disabled)”)); \WP_CLI::line(“Test mode: ” . (!empty($this->options[“test_mode”]) ? “ON” : “OFF”)); break;
case “test-mode”: $action = $assoc_args[“action”] ?? “toggle”; $current = !empty($this->options[“test_mode”]) ? 1 : 0; $new = $action === “on” ? 1 : ($action === “off” ? 0 : ($current ? 0 : 1)); $this->options[“test_mode”] = $new; update_option($this->option_name, $this->options); \WP_CLI::success(“Test mode ” . ($new ? “enabled” : “disabled”)); break;
default: \WP_CLI::line(“Usage: wp wuss <status|test-mode>”); \WP_CLI::line(” wp wuss status # Show plugin status”); \WP_CLI::line(” wp wuss test-mode –action=on|off”);
}
}
}

$wp_ultimate_ssl_security = new WP_Ultimate_SSL_Security();
register_activation_hook(__FILE__, [“WP_Ultimate_SSL_Security”, “activate”]);
register_deactivation_hook(__FILE__, [“WP_Ultimate_SSL_Security”, “deactivate”]);
register_uninstall_hook(__FILE__, [“WP_Ultimate_SSL_Security”, “uninstall”]);

Tinggalkan Balasan