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:
NixReaper
2026-07-12 14:26:59 -05:00
co-authored by Claude Opus 4.8
parent 2fec4cf34b
commit f0bc04a110
4 changed files with 225 additions and 0 deletions
+2
View File
@@ -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();
+151
View File
@@ -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);
}
}
}
+71
View File
@@ -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',
+1
View File
@@ -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";