Add Security & performance settings group + hardening module
New Settings > Security & performance group (Hardening + Performance) with toggles wired by admin/src/Security/Security.php — each gated on its own setting, adding/removing core hooks so anything left off behaves exactly like stock WordPress: - Disable XML-RPC (xmlrpc_enabled false + drop X-Pingback header) - Disable comments site-wide (close everywhere, strip post-type support, remove the Comments menu/dashboard-widget/toolbar node) - Clean up <head> (generator, RSD, WLW manifest, shortlink) - Disable emoji scripts/styles + the s.w.org dns-prefetch - Disable oEmbeds (discovery links, host js, embed REST route, wp-embed) — discovery links are removed at whatever priority core hooks them (loops, since they can be registered at more than one) - Heartbeat API: default / slow (60s) / disabled New setting keys need no schema change (Settings::merge_on_save + additionalProperties). Every toggle verified at the hook level on PHP 8.5. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2fec4cf34b
commit
f0bc04a110
@@ -46,6 +46,7 @@ use AuroraAdmin\Login\LoginStyle;
|
||||
use AuroraAdmin\Media\SvgUploads;
|
||||
use AuroraAdmin\Media\MediaFolders;
|
||||
use AuroraAdmin\ImageFormats\ImageFormats;
|
||||
use AuroraAdmin\Security\Security;
|
||||
use AuroraAdmin\Rest\MediaFolderData;
|
||||
use AuroraAdmin\Rest\BugReportData;
|
||||
use AuroraAdmin\Rest\CompanionPluginData;
|
||||
@@ -97,6 +98,7 @@ class AuroraAdmin
|
||||
new SvgUploads();
|
||||
new MediaFolders();
|
||||
new ImageFormats();
|
||||
new Security();
|
||||
new MediaFolderData();
|
||||
new BugReportData();
|
||||
new CompanionPluginData();
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
namespace AuroraAdmin\Security;
|
||||
|
||||
use AuroraAdmin\Options\Settings;
|
||||
|
||||
defined("ABSPATH") || exit();
|
||||
|
||||
/**
|
||||
* Security + performance hardening toggles (Settings > Security & performance).
|
||||
*
|
||||
* Each feature is gated on its own setting and wired here by adding/removing
|
||||
* core hooks — no output is buffered or rewritten, so anything left off
|
||||
* behaves exactly like stock WordPress. Settings are read once at construct
|
||||
* time (plugins_loaded), the same pattern the rest of the plugin uses.
|
||||
*/
|
||||
class Security
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
if (Settings::get("disable_xmlrpc")) {
|
||||
self::disable_xmlrpc();
|
||||
}
|
||||
if (Settings::get("disable_comments")) {
|
||||
self::disable_comments();
|
||||
}
|
||||
if (Settings::get("remove_head_junk")) {
|
||||
self::clean_head();
|
||||
}
|
||||
if (Settings::get("disable_emojis")) {
|
||||
self::disable_emojis();
|
||||
}
|
||||
if (Settings::get("disable_oembed")) {
|
||||
self::disable_oembed();
|
||||
}
|
||||
|
||||
$heartbeat = Settings::get("heartbeat_mode", "default");
|
||||
if ($heartbeat === "disabled") {
|
||||
add_action("init", function () {
|
||||
wp_deregister_script("heartbeat");
|
||||
}, 1);
|
||||
} elseif ($heartbeat === "slow") {
|
||||
add_filter("heartbeat_settings", function ($settings) {
|
||||
$settings["interval"] = 60;
|
||||
return $settings;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static function disable_xmlrpc()
|
||||
{
|
||||
add_filter("xmlrpc_enabled", "__return_false");
|
||||
// Drop the X-Pingback header that advertises the endpoint.
|
||||
add_filter("wp_headers", function ($headers) {
|
||||
unset($headers["X-Pingback"]);
|
||||
return $headers;
|
||||
});
|
||||
add_filter("pings_open", "__return_false", 20);
|
||||
}
|
||||
|
||||
private static function disable_comments()
|
||||
{
|
||||
// Front end: closed everywhere, and existing comments never render.
|
||||
add_filter("comments_open", "__return_false", 20);
|
||||
add_filter("pings_open", "__return_false", 20);
|
||||
add_filter("comments_array", "__return_empty_array", 20);
|
||||
|
||||
// Strip comment support from every post type.
|
||||
add_action("init", function () {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Admin: remove the Comments menu, the dashboard widget, and the toolbar
|
||||
// bubble so there's no dangling entry point.
|
||||
add_action("admin_menu", function () {
|
||||
remove_menu_page("edit-comments.php");
|
||||
});
|
||||
add_action("wp_dashboard_setup", function () {
|
||||
remove_meta_box("dashboard_recent_comments", "dashboard", "normal");
|
||||
});
|
||||
add_action("admin_bar_menu", function ($bar) {
|
||||
$bar->remove_node("comments");
|
||||
}, 999);
|
||||
}
|
||||
|
||||
private static function clean_head()
|
||||
{
|
||||
remove_action("wp_head", "rsd_link");
|
||||
remove_action("wp_head", "wlwmanifest_link");
|
||||
remove_action("wp_head", "wp_generator");
|
||||
remove_action("wp_head", "wp_shortlink_wp_head");
|
||||
add_filter("the_generator", "__return_empty_string");
|
||||
}
|
||||
|
||||
private static function disable_emojis()
|
||||
{
|
||||
remove_action("wp_head", "print_emoji_detection_script", 7);
|
||||
remove_action("admin_print_scripts", "print_emoji_detection_script");
|
||||
remove_action("wp_print_styles", "print_emoji_styles");
|
||||
remove_action("admin_print_styles", "print_emoji_styles");
|
||||
remove_filter("the_content_feed", "wp_staticize_emoji");
|
||||
remove_filter("comment_text_rss", "wp_staticize_emoji");
|
||||
remove_filter("wp_mail", "wp_staticize_emoji_for_email");
|
||||
add_filter("tiny_mce_plugins", function ($plugins) {
|
||||
return is_array($plugins) ? array_diff($plugins, ["wpemoji"]) : [];
|
||||
});
|
||||
// Drop the s.w.org DNS-prefetch the emoji script adds.
|
||||
add_filter("wp_resource_hints", function ($hints, $relation_type) {
|
||||
if ($relation_type === "dns-prefetch") {
|
||||
$emoji_svg = apply_filters("emoji_svg_url", "https://s.w.org/images/core/emoji/");
|
||||
$hints = array_filter($hints, function ($h) use ($emoji_svg) {
|
||||
return strpos((string) $h, $emoji_svg) === false && strpos((string) $h, "s.w.org") === false;
|
||||
});
|
||||
}
|
||||
return $hints;
|
||||
}, 10, 2);
|
||||
}
|
||||
|
||||
private static function disable_oembed()
|
||||
{
|
||||
// Core registers these on wp_head at a non-default priority (the
|
||||
// discovery links sit at priority 4, not 10), so match whatever priority
|
||||
// they're actually hooked at rather than assuming the default.
|
||||
self::remove_wp_head_action("wp_oembed_add_discovery_links");
|
||||
self::remove_wp_head_action("wp_oembed_add_host_js");
|
||||
add_filter("embed_oembed_discover", "__return_false");
|
||||
remove_action("rest_api_init", "wp_oembed_register_route");
|
||||
// Stop the front-end wp-embed.min.js from loading.
|
||||
add_action("wp_footer", function () {
|
||||
wp_dequeue_script("wp-embed");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a wp_head callback at whatever priority (or priorities) it's
|
||||
* registered at — core hooks the oEmbed discovery links at a non-default
|
||||
* priority, and it can end up registered at more than one, so loop until
|
||||
* none remain (bounded, so a stubborn callback can't spin forever).
|
||||
*/
|
||||
private static function remove_wp_head_action($function)
|
||||
{
|
||||
$guard = 0;
|
||||
while (($priority = has_action("wp_head", $function)) !== false && $guard++ < 20) {
|
||||
remove_action("wp_head", $function, $priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,6 +317,77 @@ export const settingsGroups = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'security',
|
||||
title: 'Security & performance',
|
||||
icon: 'dashicons-shield',
|
||||
categories: [
|
||||
{
|
||||
id: 'security-hardening',
|
||||
title: 'Hardening',
|
||||
subtitle: 'Reduce your site’s attack surface and trim unnecessary output',
|
||||
icon: 'dashicons-shield-alt',
|
||||
fields: [
|
||||
{
|
||||
key: 'disable_xmlrpc',
|
||||
type: 'toggle',
|
||||
label: 'Disable XML-RPC',
|
||||
description:
|
||||
'Turn off the XML-RPC endpoint, a common brute-force and pingback-DDoS vector. Leave on only if you rely on the WordPress mobile app or remote publishing.',
|
||||
},
|
||||
{
|
||||
key: 'disable_comments',
|
||||
type: 'toggle',
|
||||
label: 'Disable comments',
|
||||
description:
|
||||
'Turn comments off site-wide — closes them on every post type, hides existing ones, and removes the Comments admin screen and dashboard widget.',
|
||||
},
|
||||
{
|
||||
key: 'remove_head_junk',
|
||||
type: 'toggle',
|
||||
label: 'Clean up <head>',
|
||||
description:
|
||||
'Remove the WordPress generator/version tag, RSD link, WLW manifest, and shortlink meta from your site’s <head>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'performance-tweaks',
|
||||
title: 'Performance',
|
||||
subtitle: 'Trim requests and scripts WordPress loads by default',
|
||||
icon: 'dashicons-performance',
|
||||
fields: [
|
||||
{
|
||||
key: 'disable_emojis',
|
||||
type: 'toggle',
|
||||
label: 'Disable emoji scripts',
|
||||
description:
|
||||
'Stop WordPress loading its emoji-detection script and styles on every front-end and admin page.',
|
||||
},
|
||||
{
|
||||
key: 'disable_oembed',
|
||||
type: 'toggle',
|
||||
label: 'Disable oEmbeds',
|
||||
description:
|
||||
'Remove oEmbed discovery links and the wp-embed script (stops auto-embedding of pasted URLs and the embed REST route).',
|
||||
},
|
||||
{
|
||||
key: 'heartbeat_mode',
|
||||
type: 'select',
|
||||
label: 'Heartbeat API',
|
||||
description:
|
||||
'The Heartbeat API polls the server on a timer (autosave, post-lock notices). Slow it down or disable it to cut admin-ajax load.',
|
||||
default: 'default',
|
||||
options: [
|
||||
{ value: 'default', label: 'Default (15–60s)' },
|
||||
{ value: 'slow', label: 'Slow (60s)' },
|
||||
{ value: 'disabled', label: 'Disabled' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'add-ons',
|
||||
title: 'Add-ons',
|
||||
|
||||
@@ -60,6 +60,7 @@ require AURORA_ADMIN_PATH . "admin/src/Login/LoginStyle.php";
|
||||
require AURORA_ADMIN_PATH . "admin/src/Media/SvgUploads.php";
|
||||
require AURORA_ADMIN_PATH . "admin/src/Media/MediaFolders.php";
|
||||
require AURORA_ADMIN_PATH . "admin/src/ImageFormats/ImageFormats.php";
|
||||
require AURORA_ADMIN_PATH . "admin/src/Security/Security.php";
|
||||
require AURORA_ADMIN_PATH . "admin/src/Rest/MediaFolderData.php";
|
||||
require AURORA_ADMIN_PATH . "admin/src/Rest/BugReportData.php";
|
||||
require AURORA_ADMIN_PATH . "admin/src/Rest/CompanionPluginData.php";
|
||||
|
||||
Reference in New Issue
Block a user