Created
August 26, 2025 02:03
-
-
Save coulterpeterson/bb623c1edc1e74f91aaf18a79cb85955 to your computer and use it in GitHub Desktop.
WordPress Security Hardening MU Plugin (Disable Comments & More)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Plugin Name: Security Hardening & Disable Comments | |
* Description: Completely disables WordPress comments and implements security best practices | |
* Version: 2.0.0 | |
* Author: Maplespace | |
* | |
* This is a Must-Use plugin. Place this file in /wp-content/mu-plugins/ | |
*/ | |
// Prevent direct access | |
if (!defined('ABSPATH')) { | |
exit; | |
} | |
/** | |
* Main plugin class for security hardening and comment removal | |
*/ | |
class WPSecurityHardening { | |
/** | |
* Initialize all plugin features | |
*/ | |
public static function init() { | |
// Initialize comment disabling | |
self::init_disable_comments(); | |
// Initialize security hardening | |
self::init_security_hardening(); | |
// Add admin interface | |
add_action('admin_menu', [__CLASS__, 'add_admin_menu']); | |
add_action('admin_init', [__CLASS__, 'register_settings']); | |
// Handle AJAX comment deletion | |
add_action('wp_ajax_delete_all_comments_action', [__CLASS__, 'ajax_delete_all_comments']); | |
// Add admin scripts | |
add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue_admin_scripts']); | |
} | |
/** | |
* Initialize comment disabling features | |
*/ | |
private static function init_disable_comments() { | |
// Admin-specific actions | |
add_action('admin_init', [__CLASS__, 'admin_init']); | |
add_action('admin_menu', [__CLASS__, 'remove_comment_menus'], 999); | |
// Frontend comment blocking | |
add_filter('comments_open', '__return_false', 20, 2); | |
add_filter('pings_open', '__return_false', 20, 2); | |
add_filter('comments_array', '__return_empty_array', 10, 2); | |
// Admin bar cleanup | |
add_action('init', [__CLASS__, 'remove_admin_bar_comments']); | |
add_action('wp_before_admin_bar_render', [__CLASS__, 'remove_admin_bar_comments_css']); | |
// Remove comment feeds | |
add_filter('wp_headers', [__CLASS__, 'remove_x_pingback']); | |
add_action('template_redirect', [__CLASS__, 'remove_comment_feed']); | |
// Remove comment widgets | |
add_action('widgets_init', [__CLASS__, 'remove_comment_widgets']); | |
// Remove comments from REST API | |
add_filter('rest_endpoints', [__CLASS__, 'remove_comment_endpoints']); | |
// Remove comment support from post types | |
add_action('init', [__CLASS__, 'remove_comment_support'], 100); | |
} | |
/** | |
* Initialize security hardening features | |
*/ | |
private static function init_security_hardening() { | |
// Hide WordPress version | |
remove_action('wp_head', 'wp_generator'); | |
add_filter('the_generator', '__return_empty_string'); | |
add_filter('style_loader_src', [__CLASS__, 'remove_version_strings'], 9999); | |
add_filter('script_loader_src', [__CLASS__, 'remove_version_strings'], 9999); | |
// Remove various WordPress headers | |
remove_action('wp_head', 'rsd_link'); | |
remove_action('wp_head', 'wlwmanifest_link'); | |
remove_action('wp_head', 'wp_shortlink_wp_head'); | |
remove_action('wp_head', 'rest_output_link_wp_head'); | |
remove_action('wp_head', 'wp_oembed_add_discovery_links'); | |
remove_action('template_redirect', 'rest_output_link_header', 11); | |
// Remove emoji support (performance improvement) | |
remove_action('wp_head', 'print_emoji_detection_script', 7); | |
remove_action('wp_print_styles', 'print_emoji_styles'); | |
remove_action('admin_print_scripts', 'print_emoji_detection_script'); | |
remove_action('admin_print_styles', 'print_emoji_styles'); | |
add_filter('emoji_svg_url', '__return_false'); | |
// Disable XML-RPC | |
add_filter('xmlrpc_enabled', '__return_false'); | |
add_filter('pings_open', '__return_false'); | |
// Remove DNS prefetch | |
remove_action('wp_head', 'wp_resource_hints', 2); | |
// Security headers | |
add_action('send_headers', [__CLASS__, 'add_security_headers']); | |
// Hide login errors (prevents username enumeration) | |
add_filter('login_errors', function() { | |
return __('Login details are incorrect.'); | |
}); | |
// Disable file editing in admin | |
if (!defined('DISALLOW_FILE_EDIT')) { | |
define('DISALLOW_FILE_EDIT', true); | |
} | |
// Remove unnecessary dashboard widgets | |
add_action('wp_dashboard_setup', [__CLASS__, 'remove_dashboard_widgets']); | |
// Disable author archives to prevent user enumeration | |
add_action('template_redirect', [__CLASS__, 'disable_author_archives']); | |
// Remove user enumeration via REST API | |
add_filter('rest_endpoints', [__CLASS__, 'restrict_user_endpoints']); | |
} | |
/** | |
* Admin initialization for comment disabling | |
*/ | |
public static function admin_init() { | |
// Redirect from comments page (but not if we're on our settings page) | |
global $pagenow; | |
$current_page = isset($_GET['page']) ? $_GET['page'] : ''; | |
if ($pagenow === 'edit-comments.php') { | |
wp_safe_redirect(admin_url()); | |
exit; | |
} | |
// Only redirect from discussion page if it's not our custom page | |
if ($pagenow === 'options-discussion.php' && $current_page !== 'security-hardening') { | |
wp_safe_redirect(admin_url('options-general.php?page=security-hardening')); | |
exit; | |
} | |
// Remove comments metabox from dashboard | |
remove_meta_box('dashboard_recent_comments', 'dashboard', 'normal'); | |
// Remove comments column from post/page lists | |
add_filter('manage_posts_columns', [__CLASS__, 'remove_comments_column']); | |
add_filter('manage_pages_columns', [__CLASS__, 'remove_comments_column']); | |
// Remove Quick Edit comment options | |
add_filter('bulk_actions-edit-post', [__CLASS__, 'remove_bulk_comments']); | |
add_filter('bulk_actions-edit-page', [__CLASS__, 'remove_bulk_comments']); | |
} | |
/** | |
* Remove comment support from all post types | |
*/ | |
public static function remove_comment_support() { | |
foreach (get_post_types() as $post_type) { | |
if (post_type_supports($post_type, 'comments')) { | |
remove_post_type_support($post_type, 'comments'); | |
remove_post_type_support($post_type, 'trackbacks'); | |
} | |
} | |
} | |
/** | |
* Add admin menu for settings | |
*/ | |
public static function add_admin_menu() { | |
add_options_page( | |
'Security & Comments Settings', | |
'Security Hardening', | |
'manage_options', | |
'security-hardening', | |
[__CLASS__, 'admin_page'] | |
); | |
} | |
/** | |
* Register plugin settings | |
*/ | |
public static function register_settings() { | |
register_setting('security_hardening_settings', 'security_hardening_options'); | |
} | |
/** | |
* Admin page HTML | |
*/ | |
public static function admin_page() { | |
if (!current_user_can('manage_options')) { | |
return; | |
} | |
// Get comment statistics | |
global $wpdb; | |
$comment_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments}"); | |
$spam_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_approved = 'spam'"); | |
$trash_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_approved = 'trash'"); | |
$pending_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_approved = '0'"); | |
?> | |
<div class="wrap"> | |
<h1><?php echo esc_html(get_admin_page_title()); ?></h1> | |
<div class="notice notice-info"> | |
<p><strong>Active Features:</strong></p> | |
<ul style="list-style: disc; margin-left: 20px;"> | |
<li>✓ Comments completely disabled</li> | |
<li>✓ WordPress version hidden</li> | |
<li>✓ XML-RPC disabled</li> | |
<li>✓ Security headers added</li> | |
<li>✓ Login error messages obscured</li> | |
<li>✓ File editing disabled</li> | |
<li>✓ User enumeration blocked</li> | |
<li>✓ Unnecessary headers removed</li> | |
</ul> | |
</div> | |
<div class="card" style="max-width: 600px; margin-top: 20px;"> | |
<h2>Comment Management</h2> | |
<p>Comments are currently <strong style="color: red;">DISABLED</strong> site-wide.</p> | |
<h3>Comment Statistics</h3> | |
<table class="widefat" style="max-width: 400px;"> | |
<tbody> | |
<tr> | |
<td><strong>Total Comments:</strong></td> | |
<td><?php echo number_format($comment_count); ?></td> | |
</tr> | |
<tr> | |
<td><strong>Spam Comments:</strong></td> | |
<td><?php echo number_format($spam_count); ?></td> | |
</tr> | |
<tr> | |
<td><strong>Trashed Comments:</strong></td> | |
<td><?php echo number_format($trash_count); ?></td> | |
</tr> | |
<tr> | |
<td><strong>Pending Comments:</strong></td> | |
<td><?php echo number_format($pending_count); ?></td> | |
</tr> | |
</tbody> | |
</table> | |
<?php if ($comment_count > 0): ?> | |
<div style="margin-top: 20px; padding: 15px; background: #fff; border: 1px solid #c3c4c7; border-left: 4px solid #d63638;"> | |
<h3 style="margin-top: 0; color: #d63638;">⚠️ Delete All Comments</h3> | |
<p><strong>Warning:</strong> This action will permanently delete ALL comments from your database. This cannot be undone!</p> | |
<p> | |
<label> | |
<input type="checkbox" id="confirm-delete-comments" /> | |
I understand this will permanently delete <?php echo number_format($comment_count); ?> comment(s) | |
</label> | |
</p> | |
<button type="button" | |
id="delete-all-comments-btn" | |
class="button button-primary" | |
disabled | |
style="background: #d63638; border-color: #d63638;"> | |
Delete All Comments Permanently | |
</button> | |
<div id="delete-comments-result" style="margin-top: 15px;"></div> | |
</div> | |
<?php else: ?> | |
<div style="margin-top: 20px; padding: 15px; background: #f0f8f0; border: 1px solid #c3c4c7; border-left: 4px solid #00a32a;"> | |
<p style="margin: 0;">✓ No comments found in the database.</p> | |
</div> | |
<?php endif; ?> | |
</div> | |
<div class="card" style="max-width: 600px; margin-top: 20px;"> | |
<h2>Security Status</h2> | |
<p>The following security enhancements are active:</p> | |
<ul style="list-style: disc; margin-left: 20px;"> | |
<li>WordPress version information removed from HTML, RSS feeds, scripts, and styles</li> | |
<li>XML-RPC completely disabled (prevents brute force attacks)</li> | |
<li>Login error messages unified (prevents username enumeration)</li> | |
<li>File editing disabled in WordPress admin</li> | |
<li>Author archives disabled (prevents user enumeration)</li> | |
<li>User REST API endpoints restricted</li> | |
<li>Security headers added (X-Content-Type-Options, X-Frame-Options, etc.)</li> | |
<li>Unnecessary WordPress headers and features removed</li> | |
</ul> | |
</div> | |
</div> | |
<?php | |
} | |
/** | |
* Enqueue admin scripts | |
*/ | |
public static function enqueue_admin_scripts($hook) { | |
if ($hook !== 'settings_page_security-hardening') { | |
return; | |
} | |
?> | |
<script type="text/javascript"> | |
document.addEventListener('DOMContentLoaded', function() { | |
const checkbox = document.getElementById('confirm-delete-comments'); | |
const deleteBtn = document.getElementById('delete-all-comments-btn'); | |
const resultDiv = document.getElementById('delete-comments-result'); | |
if (!checkbox || !deleteBtn) { | |
return; | |
} | |
// Handle checkbox change | |
checkbox.addEventListener('change', function() { | |
deleteBtn.disabled = !this.checked; | |
}); | |
// Handle delete button click | |
deleteBtn.addEventListener('click', function() { | |
if (!confirm('Are you absolutely sure? This will permanently delete ALL comments!')) { | |
return; | |
} | |
// Disable button and update text | |
deleteBtn.disabled = true; | |
deleteBtn.textContent = 'Deleting...'; | |
// Prepare form data | |
const formData = new FormData(); | |
formData.append('action', 'delete_all_comments_action'); | |
formData.append('nonce', '<?php echo wp_create_nonce('delete_all_comments_nonce'); ?>'); | |
// Make AJAX request | |
fetch(ajaxurl, { | |
method: 'POST', | |
credentials: 'same-origin', | |
body: formData | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
if (data.success) { | |
resultDiv.innerHTML = '<div class="notice notice-success"><p>' + data.data.message + '</p></div>'; | |
deleteBtn.textContent = 'Deleted Successfully'; | |
setTimeout(function() { | |
location.reload(); | |
}, 2000); | |
} else { | |
resultDiv.innerHTML = '<div class="notice notice-error"><p>Error: ' + data.data.message + '</p></div>'; | |
deleteBtn.disabled = false; | |
deleteBtn.textContent = 'Delete All Comments Permanently'; | |
checkbox.checked = false; | |
} | |
}) | |
.catch(error => { | |
console.error('Error:', error); | |
resultDiv.innerHTML = '<div class="notice notice-error"><p>An error occurred. Please try again.</p></div>'; | |
deleteBtn.disabled = false; | |
deleteBtn.textContent = 'Delete All Comments Permanently'; | |
checkbox.checked = false; | |
}); | |
}); | |
}); | |
</script> | |
<?php | |
} | |
/** | |
* AJAX handler for deleting all comments | |
*/ | |
public static function ajax_delete_all_comments() { | |
// Check nonce and capabilities | |
if (!wp_verify_nonce($_POST['nonce'], 'delete_all_comments_nonce') || !current_user_can('manage_options')) { | |
wp_send_json_error(['message' => 'Security check failed']); | |
} | |
global $wpdb; | |
// Get count before deletion | |
$count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments}"); | |
// Delete all comments | |
$wpdb->query("DELETE FROM {$wpdb->comments}"); | |
// Delete all comment meta | |
$wpdb->query("DELETE FROM {$wpdb->commentmeta}"); | |
// Reset comment counts for all posts | |
$wpdb->query("UPDATE {$wpdb->posts} SET comment_count = 0"); | |
wp_send_json_success([ | |
'message' => sprintf('Successfully deleted %s comment(s) from the database.', number_format($count)) | |
]); | |
} | |
/** | |
* Remove comment menus | |
*/ | |
public static function remove_comment_menus() { | |
remove_menu_page('edit-comments.php'); | |
// Remove Discussion from Settings menu, but only if it's not our page | |
global $submenu; | |
if (isset($submenu['options-general.php'])) { | |
foreach ($submenu['options-general.php'] as $index => $menu_item) { | |
if ($menu_item[2] === 'options-discussion.php') { | |
unset($submenu['options-general.php'][$index]); | |
} | |
} | |
} | |
} | |
/** | |
* Remove version strings from scripts and styles | |
*/ | |
public static function remove_version_strings($src) { | |
if (strpos($src, '?ver=')) { | |
$src = remove_query_arg('ver', $src); | |
} | |
return $src; | |
} | |
/** | |
* Add security headers | |
*/ | |
public static function add_security_headers() { | |
header('X-Content-Type-Options: nosniff'); | |
header('X-Frame-Options: SAMEORIGIN'); | |
header('X-XSS-Protection: 1; mode=block'); | |
header('Referrer-Policy: strict-origin-when-cross-origin'); | |
header('Permissions-Policy: geolocation=(), microphone=(), camera=()'); | |
} | |
/** | |
* Remove dashboard widgets | |
*/ | |
public static function remove_dashboard_widgets() { | |
remove_meta_box('dashboard_primary', 'dashboard', 'side'); // WordPress news | |
remove_meta_box('dashboard_secondary', 'dashboard', 'side'); // Other WordPress news | |
remove_meta_box('dashboard_quick_press', 'dashboard', 'side'); // Quick Press | |
remove_meta_box('dashboard_recent_drafts', 'dashboard', 'side'); // Recent Drafts | |
} | |
/** | |
* Disable author archives | |
*/ | |
public static function disable_author_archives() { | |
if (is_author()) { | |
global $wp_query; | |
$wp_query->set_404(); | |
status_header(404); | |
} | |
} | |
/** | |
* Restrict user endpoints in REST API | |
*/ | |
public static function restrict_user_endpoints($endpoints) { | |
if (!is_user_logged_in() || !current_user_can('list_users')) { | |
if (isset($endpoints['/wp/v2/users'])) { | |
unset($endpoints['/wp/v2/users']); | |
} | |
if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) { | |
unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']); | |
} | |
} | |
return $endpoints; | |
} | |
/** | |
* Remove comments from admin bar | |
*/ | |
public static function remove_admin_bar_comments() { | |
if (is_admin_bar_showing()) { | |
remove_action('admin_bar_menu', 'wp_admin_bar_comments_menu', 60); | |
} | |
} | |
/** | |
* Remove comments CSS from admin bar | |
*/ | |
public static function remove_admin_bar_comments_css() { | |
global $wp_admin_bar; | |
$wp_admin_bar->remove_menu('comments'); | |
} | |
/** | |
* Remove comments column from post listings | |
*/ | |
public static function remove_comments_column($columns) { | |
unset($columns['comments']); | |
return $columns; | |
} | |
/** | |
* Remove bulk comment actions | |
*/ | |
public static function remove_bulk_comments($actions) { | |
unset($actions['edit']); | |
return $actions; | |
} | |
/** | |
* Remove X-Pingback HTTP header | |
*/ | |
public static function remove_x_pingback($headers) { | |
unset($headers['X-Pingback']); | |
return $headers; | |
} | |
/** | |
* Disable comment feeds | |
*/ | |
public static function remove_comment_feed() { | |
if (is_comment_feed()) { | |
wp_die(__('Comments are disabled on this site.'), '', ['response' => 403]); | |
} | |
} | |
/** | |
* Remove comment widgets | |
*/ | |
public static function remove_comment_widgets() { | |
unregister_widget('WP_Widget_Recent_Comments'); | |
} | |
/** | |
* Remove comment endpoints from REST API | |
*/ | |
public static function remove_comment_endpoints($endpoints) { | |
if (isset($endpoints['/wp/v2/comments'])) { | |
unset($endpoints['/wp/v2/comments']); | |
} | |
if (isset($endpoints['/wp/v2/comments/(?P<id>[\d]+)'])) { | |
unset($endpoints['/wp/v2/comments/(?P<id>[\d]+)']); | |
} | |
return $endpoints; | |
} | |
} | |
// Initialize the plugin | |
WPSecurityHardening::init(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment