From f0bc04a110bc561449a3ac20d3f963f93eb0df06 Mon Sep 17 00:00:00 2001 From: NixReaper Date: Sun, 12 Jul 2026 14:26:59 -0500 Subject: [PATCH] Add Security & performance settings group + hardening module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 (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 --- admin/src/App/AuroraAdmin.php | 2 + admin/src/Security/Security.php | 151 +++++++++++++++++++++++ app/src/pages/settings/settingsConfig.js | 71 +++++++++++ aurora-admin.php | 1 + 4 files changed, 225 insertions(+) create mode 100644 admin/src/Security/Security.php diff --git a/admin/src/App/AuroraAdmin.php b/admin/src/App/AuroraAdmin.php index e0f2516..64d480e 100644 --- a/admin/src/App/AuroraAdmin.php +++ b/admin/src/App/AuroraAdmin.php @@ -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(); diff --git a/admin/src/Security/Security.php b/admin/src/Security/Security.php new file mode 100644 index 0000000..519e427 --- /dev/null +++ b/admin/src/Security/Security.php @@ -0,0 +1,151 @@ + 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); + } + } +} diff --git a/app/src/pages/settings/settingsConfig.js b/app/src/pages/settings/settingsConfig.js index 9443f6a..2603a95 100644 --- a/app/src/pages/settings/settingsConfig.js +++ b/app/src/pages/settings/settingsConfig.js @@ -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 ', + description: + 'Remove the WordPress generator/version tag, RSD link, WLW manifest, and shortlink meta from your site’s .', + }, + ], + }, + { + 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', diff --git a/aurora-admin.php b/aurora-admin.php index 391ed95..8dce3e3 100644 --- a/aurora-admin.php +++ b/aurora-admin.php @@ -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";