Changeset 3492634
- Timestamp:
- 03/27/2026 12:15:52 PM (19 hours ago)
- Location:
- security-hardener
- Files:
-
- 7 edited
-
assets/blueprints/blueprint.json (modified) (1 diff)
-
readme.txt (modified) (2 diffs)
-
security-hardener.php (modified) (15 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/security-hardener.php (modified) (15 diffs)
-
trunk/uninstall.php (modified) (1 diff)
-
uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
security-hardener/assets/blueprints/blueprint.json
r3487909 r3492634 16 16 "pluginZipFile": { 17 17 "resource": "url", 18 "url": "https://downloads.wordpress.org/plugin/security-hardener.2. 0.2.zip"18 "url": "https://downloads.wordpress.org/plugin/security-hardener.2.1.0.zip" 19 19 }, 20 20 "options": { -
security-hardener/readme.txt
r3487909 r3492634 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.2 7 Stable tag: 2. 0.27 Stable tag: 2.1.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 163 163 164 164 == Changelog == 165 166 = 2.1.0 - 2026-03-26 = 167 * Added: Interactive hardening checklist — users can mark each recommendation as done. 168 * Improved: File permissions check moved from floating admin notice into the settings page. 169 * Fixed: "Enable preload" description now clarifies it only adds the preload directive to the HSTS header. 170 * Fixed: "Clean wp_head" description no longer uses HTML entities that rendered as literal text in some contexts. 171 * Fixed: wp-includes recommendation text no longer implies a code snippet follows. 165 172 166 173 = 2.0.2 - 2026-03-21 = -
security-hardener/security-hardener.php
r3487909 r3492634 4 4 Plugin URI: https://wordpress.org/plugins/security-hardener/ 5 5 Description: Basic hardening: secure headers, disable XML-RPC/pingbacks, hide version, block user enumeration, generic login errors, and IP-based rate limiting. 6 Version: 2. 0.26 Version: 2.1.0 7 7 Requires at least: 6.9 8 8 Tested up to: 6.9 … … 20 20 21 21 // Plugin constants 22 define( 'WPSH_VERSION', '2. 0.2' );22 define( 'WPSH_VERSION', '2.1.0' ); 23 23 define( 'WPSH_FILE', __FILE__ ); 24 24 define( 'WPSH_BASENAME', plugin_basename( __FILE__ ) ); … … 35 35 */ 36 36 const OPTION_NAME = 'wpsh_options'; 37 38 /** 39 * Checklist state option name in database 40 */ 41 const CHECKLIST_OPTION = 'wpsh_checklist'; 37 42 38 43 /** … … 140 145 add_action( 'admin_notices', array( $this, 'show_admin_notices' ) ); 141 146 add_filter( 'plugin_action_links_' . WPSH_BASENAME, array( $this, 'add_settings_link' ) ); 147 add_action( 'wp_ajax_wpsh_toggle_checklist', array( $this, 'ajax_toggle_checklist' ) ); 148 add_action( 'wp_ajax_wpsh_reset_checklist', array( $this, 'ajax_reset_checklist' ) ); 142 149 } 143 150 } … … 808 815 .wpsh-recommendations li{margin-bottom:4px;} 809 816 .wpsh-save-bar{margin:20px 0 4px;} 817 .wpsh-cl-header{display:flex;align-items:center;justify-content:space-between;margin-top:24px;} 818 .wpsh-cl-progress{display:flex;align-items:center;gap:8px;} 819 .wpsh-cl-bar{height:6px;width:120px;background:#c3c4c7;border-radius:3px;overflow:hidden;} 820 .wpsh-cl-bar-fill{height:100%;background:#2271b1;border-radius:3px;transition:width .2s;} 821 .wpsh-cl-pct{font-size:12px;color:#646970;white-space:nowrap;} 822 .wpsh-cl-item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid #f0f0f1;cursor:pointer;transition:background .1s;} 823 .wpsh-cl-item:last-child{border-bottom:none;} 824 .wpsh-cl-item:hover{background:#f6f7f7;} 825 .wpsh-cl-item:focus{outline:2px solid #2271b1;outline-offset:-2px;} 826 .wpsh-cl-item.wpsh-cl-done{background:#f0f6f0;} 827 .wpsh-cl-check{flex-shrink:0;width:18px;height:18px;border:1.5px solid #c3c4c7;border-radius:3px;margin-top:1px;display:flex;align-items:center;justify-content:center;transition:all .15s;} 828 .wpsh-cl-item.wpsh-cl-done .wpsh-cl-check{background:#2271b1;border-color:#2271b1;} 829 .wpsh-cl-check svg{display:none;} 830 .wpsh-cl-item.wpsh-cl-done .wpsh-cl-check svg{display:block;} 831 .wpsh-cl-label{font-size:13px;line-height:1.45;color:#1d2327;} 832 .wpsh-cl-item.wpsh-cl-done .wpsh-cl-label{color:#646970;text-decoration:line-through;text-decoration-color:#c3c4c7;} 810 833 @media(max-width:1200px){.wpsh-grid{grid-template-columns:repeat(2,minmax(0,1fr));}} 811 834 @media(max-width:782px){.wpsh-grid{grid-template-columns:minmax(0,1fr);}} … … 884 907 885 908 /** 909 * AJAX handler — toggle a single checklist item. 910 * 911 * Expects POST: nonce, item_id (int 0-13), checked (0|1). 912 */ 913 public function ajax_toggle_checklist(): void { 914 check_ajax_referer( 'wpsh_checklist_nonce', 'nonce' ); 915 916 if ( ! current_user_can( 'manage_options' ) ) { 917 wp_send_json_error( 'Unauthorized', 403 ); 918 } 919 920 $item_id = isset( $_POST['item_id'] ) ? absint( $_POST['item_id'] ) : null; 921 $checked = isset( $_POST['checked'] ) && '1' === $_POST['checked']; 922 923 if ( null === $item_id || $item_id > 13 ) { 924 wp_send_json_error( 'Invalid item', 400 ); 925 } 926 927 $state = get_option( self::CHECKLIST_OPTION, [] ); 928 929 if ( $checked ) { 930 $state[ $item_id ] = true; 931 } else { 932 unset( $state[ $item_id ] ); 933 } 934 935 update_option( self::CHECKLIST_OPTION, $state, false ); 936 wp_send_json_success( [ 'done' => count( $state ), 'total' => 14 ] ); 937 } 938 939 /** 940 * AJAX handler — reset all checklist items. 941 * 942 * Expects POST: nonce. 943 */ 944 public function ajax_reset_checklist(): void { 945 check_ajax_referer( 'wpsh_checklist_nonce', 'nonce' ); 946 947 if ( ! current_user_can( 'manage_options' ) ) { 948 wp_send_json_error( 'Unauthorized', 403 ); 949 } 950 951 update_option( self::CHECKLIST_OPTION, [], false ); 952 wp_send_json_success( [ 'done' => 0, 'total' => 14 ] ); 953 } 954 955 /** 956 * Render the interactive hardening recommendations checklist. 957 * 958 * State is persisted in wpsh_checklist via AJAX (no page reload needed). 959 */ 960 private function render_checklist(): void { 961 $state = get_option( self::CHECKLIST_OPTION, [] ); 962 $done = count( $state ); 963 $total = 14; 964 $pct = $total > 0 ? round( $done / $total * 100 ) : 0; 965 966 $items = [ 967 __( 'Use strong passwords and enable two-factor authentication', 'security-hardener' ), 968 __( 'Keep WordPress, themes, and plugins updated', 'security-hardener' ), 969 __( 'Use HTTPS (SSL/TLS) for your entire site', 'security-hardener' ), 970 __( 'Regular backups stored off-site', 'security-hardener' ), 971 __( 'Limit login attempts at the server/firewall level', 'security-hardener' ), 972 __( 'Use security plugins for malware scanning', 'security-hardener' ), 973 __( 'Restrict file permissions (directories: 755, files: 644)', 'security-hardener' ), 974 __( 'Consider using a Web Application Firewall (WAF)', 'security-hardener' ), 975 __( 'Protect the wp-admin directory with an additional HTTP authentication layer (BasicAuth)', 'security-hardener' ), 976 __( 'Change the default database table prefix from wp_ to a custom value', 'security-hardener' ), 977 __( 'Rename the default admin account to a non-obvious username', 'security-hardener' ), 978 __( 'Restrict database user privileges to SELECT, INSERT, UPDATE and DELETE only', 'security-hardener' ), 979 __( 'Protect wp-config.php by moving it one directory above the WordPress root or restricting access via .htaccess', 'security-hardener' ), 980 __( 'Block direct access to files in wp-includes/ via .htaccess rules — see the WordPress Hardening Guide for the full snippet.', 'security-hardener' ), 981 ]; 982 983 $nonce = wp_create_nonce( 'wpsh_checklist_nonce' ); 984 ?> 985 <hr style="margin: 24px 0;"> 986 987 <div class="wpsh-cl-header"> 988 <h2 style="margin:0;"><?php esc_html_e( 'Additional Hardening Recommendations', 'security-hardener' ); ?></h2> 989 <div class="wpsh-cl-progress"> 990 <div class="wpsh-cl-bar"> 991 <div class="wpsh-cl-bar-fill" id="wpsh-bar-fill" style="width:<?php echo absint( $pct ); ?>%"></div> 992 </div> 993 <span class="wpsh-cl-pct" id="wpsh-cl-pct"> 994 <?php 995 printf( 996 /* translators: 1: completed items, 2: total items */ 997 esc_html__( '%1$d / %2$d done', 'security-hardener' ), 998 absint( $done ), 999 absint( $total ) 1000 ); 1001 ?> 1002 </span> 1003 </div> 1004 </div> 1005 1006 <div class="wpsh-card" style="margin-top:12px;" id="wpsh-checklist"> 1007 <?php foreach ( $items as $id => $label ) : ?> 1008 <div class="wpsh-cl-item<?php echo isset( $state[ $id ] ) ? ' wpsh-cl-done' : ''; ?>" 1009 data-id="<?php echo absint( $id ); ?>" 1010 data-nonce="<?php echo esc_attr( $nonce ); ?>" 1011 role="checkbox" 1012 aria-checked="<?php echo isset( $state[ $id ] ) ? 'true' : 'false'; ?>" 1013 tabindex="0"> 1014 <div class="wpsh-cl-check" aria-hidden="true"> 1015 <svg width="10" height="8" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg"> 1016 <path d="M1 4l3 3 5-6" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/> 1017 </svg> 1018 </div> 1019 <span class="wpsh-cl-label"><?php echo esc_html( $label ); ?></span> 1020 </div> 1021 <?php endforeach; ?> 1022 </div> 1023 1024 <div style="display:flex;justify-content:space-between;align-items:center;margin-top:10px;"> 1025 <p style="margin:0;font-size:12px;color:#646970;"> 1026 <?php esc_html_e( 'Your progress is saved automatically.', 'security-hardener' ); ?> 1027 </p> 1028 <button type="button" id="wpsh-cl-reset" class="button-link" 1029 data-nonce="<?php echo esc_attr( $nonce ); ?>" 1030 style="font-size:12px;color:#2271b1;"> 1031 <?php esc_html_e( 'Reset all', 'security-hardener' ); ?> 1032 </button> 1033 </div> 1034 1035 <p style="margin-top:16px;"> 1036 <?php 1037 printf( 1038 /* translators: %s: URL to WordPress hardening guide */ 1039 wp_kses_post( __( 'For more information, see the official <a href="%s" target="_blank">WordPress Hardening Guide</a>.', 'security-hardener' ) ), 1040 'https://developer.wordpress.org/advanced-administration/security/hardening/' 1041 ); 1042 ?> 1043 </p> 1044 1045 <script> 1046 (function() { 1047 var ajaxUrl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>; 1048 var total = <?php echo absint( $total ); ?>; 1049 1050 function updateProgress( done ) { 1051 var pct = total > 0 ? Math.round( done / total * 100 ) : 0; 1052 document.getElementById( 'wpsh-bar-fill' ).style.width = pct + '%'; 1053 document.getElementById( 'wpsh-cl-pct' ).textContent = done + ' / ' + total + ' done'; 1054 } 1055 1056 function sendToggle( id, checked, nonce ) { 1057 var fd = new FormData(); 1058 fd.append( 'action', 'wpsh_toggle_checklist' ); 1059 fd.append( 'nonce', nonce ); 1060 fd.append( 'item_id', id ); 1061 fd.append( 'checked', checked ? '1' : '0' ); 1062 fetch( ajaxUrl, { method: 'POST', body: fd, credentials: 'same-origin' } ) 1063 .then( function( r ) { return r.json(); } ) 1064 .then( function( data ) { if ( data.success ) updateProgress( data.data.done ); } ); 1065 } 1066 1067 document.querySelectorAll( '.wpsh-cl-item' ).forEach( function( el ) { 1068 function toggle() { 1069 var done = el.classList.toggle( 'wpsh-cl-done' ); 1070 var checked = el.classList.contains( 'wpsh-cl-done' ); 1071 el.setAttribute( 'aria-checked', checked ? 'true' : 'false' ); 1072 sendToggle( el.dataset.id, checked, el.dataset.nonce ); 1073 } 1074 el.addEventListener( 'click', toggle ); 1075 el.addEventListener( 'keydown', function( e ) { 1076 if ( e.key === ' ' || e.key === 'Enter' ) { e.preventDefault(); toggle(); } 1077 } ); 1078 } ); 1079 1080 var resetBtn = document.getElementById( 'wpsh-cl-reset' ); 1081 if ( resetBtn ) { 1082 resetBtn.addEventListener( 'click', function() { 1083 var fd = new FormData(); 1084 fd.append( 'action', 'wpsh_reset_checklist' ); 1085 fd.append( 'nonce', resetBtn.dataset.nonce ); 1086 fetch( ajaxUrl, { method: 'POST', body: fd, credentials: 'same-origin' } ) 1087 .then( function( r ) { return r.json(); } ) 1088 .then( function( data ) { 1089 if ( data.success ) { 1090 document.querySelectorAll( '.wpsh-cl-item' ).forEach( function( el ) { 1091 el.classList.remove( 'wpsh-cl-done' ); 1092 el.setAttribute( 'aria-checked', 'false' ); 1093 } ); 1094 updateProgress( 0 ); 1095 } 1096 } ); 1097 } ); 1098 } 1099 })(); 1100 </script> 1101 <?php 1102 } 1103 1104 /** 886 1105 * Render settings page 887 1106 */ … … 1066 1285 sprintf( 1067 1286 /* translators: %s: URL to HSTS preload list */ 1068 wp_kses_post( __( ' Submit to the <a href="%s" target="_blank">HSTS Preload List</a>. Requires 1 year max-age.', 'security-hardener' ) ),1287 wp_kses_post( __( 'Adds the preload directive to the HSTS header. Required before submitting manually to <a href="%s" target="_blank">hstspreload.org</a>.', 'security-hardener' ) ), 1069 1288 'https://hstspreload.org/' 1070 1289 ) … … 1089 1308 'clean_head', 1090 1309 __( 'Clean wp_head', 'security-hardener' ), 1091 __( 'Removes RSD link, Windows Live Writer manifest, shortlink, and emoji scripts from <head>.', 'security-hardener' )1310 __( 'Removes RSD link, Windows Live Writer manifest, shortlink, and emoji scripts from the page head.', 'security-hardener' ) 1092 1311 ); 1093 1312 $this->render_toggle_row( … … 1116 1335 <?php $this->render_security_logs(); ?> 1117 1336 1118 <hr style="margin: 24px 0;"> 1119 1120 <h2><?php esc_html_e( 'Additional Hardening Recommendations', 'security-hardener' ); ?></h2> 1121 <ul class="wpsh-recommendations" style="list-style: disc; padding-left: 20px;"> 1122 <li><?php esc_html_e( 'Use strong passwords and enable two-factor authentication', 'security-hardener' ); ?></li> 1123 <li><?php esc_html_e( 'Keep WordPress, themes, and plugins updated', 'security-hardener' ); ?></li> 1124 <li><?php esc_html_e( 'Use HTTPS (SSL/TLS) for your entire site', 'security-hardener' ); ?></li> 1125 <li><?php esc_html_e( 'Regular backups stored off-site', 'security-hardener' ); ?></li> 1126 <li><?php esc_html_e( 'Limit login attempts at the server/firewall level', 'security-hardener' ); ?></li> 1127 <li><?php esc_html_e( 'Use security plugins for malware scanning', 'security-hardener' ); ?></li> 1128 <li><?php esc_html_e( 'Restrict file permissions (directories: 755, files: 644)', 'security-hardener' ); ?></li> 1129 <li><?php esc_html_e( 'Consider using a Web Application Firewall (WAF)', 'security-hardener' ); ?></li> 1130 <li><?php esc_html_e( 'Protect the wp-admin directory with an additional HTTP authentication layer (BasicAuth)', 'security-hardener' ); ?></li> 1131 <li><?php esc_html_e( 'Change the default database table prefix from wp_ to a custom value', 'security-hardener' ); ?></li> 1132 <li><?php esc_html_e( 'Rename the default admin account to a non-obvious username', 'security-hardener' ); ?></li> 1133 <li><?php esc_html_e( 'Restrict database user privileges to SELECT, INSERT, UPDATE and DELETE only', 'security-hardener' ); ?></li> 1134 <li><?php esc_html_e( 'Protect wp-config.php by moving it one directory above the WordPress root or restricting access via .htaccess', 'security-hardener' ); ?></li> 1135 <li><?php esc_html_e( 'Block direct access to files in wp-includes/ by adding the following rules to your .htaccess file (outside the WordPress tags).', 'security-hardener' ); ?></li> 1136 </ul> 1137 <p> 1138 <?php 1139 printf( 1140 /* translators: %s: URL to WordPress hardening guide */ 1141 wp_kses_post( __( 'For more information, see the official <a href="%s" target="_blank">WordPress Hardening Guide</a>.', 'security-hardener' ) ), 1142 'https://developer.wordpress.org/advanced-administration/security/hardening/' 1143 ); 1144 ?> 1145 </p> 1337 <?php $this->render_file_permissions(); ?> 1338 1339 <?php $this->render_checklist(); ?> 1146 1340 1147 1341 </div><!-- .wrap --> … … 1221 1415 } 1222 1416 1223 // Check file permissions 1224 $this->check_file_permissions_notice(); 1225 } 1226 1227 /** 1228 * Check file permissions and show notice 1229 */ 1230 private function check_file_permissions_notice(): void { 1231 // Only show on plugin settings page 1232 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; 1233 if ( ! $screen || 'settings_page_security-hardener' !== $screen->id ) { 1234 return; 1235 } 1236 1417 // Check file permissions is now rendered inline in render_settings_page() 1418 } 1419 1420 /** 1421 * Render file permissions check as an inline section on the settings page. 1422 * 1423 * Shows a success notice when all paths are correct, or a table listing 1424 * only the paths with issues when problems are found. 1425 */ 1426 private function render_file_permissions(): void { 1237 1427 if ( ! function_exists( 'get_home_path' ) ) { 1238 1428 require_once ABSPATH . 'wp-admin/includes/file.php'; … … 1243 1433 array( 1244 1434 'path' => ABSPATH . 'wp-config.php', 1245 'type' => 'file',1246 1435 'recommended' => array( '0600', '0640', '0644' ), 1247 1436 'label' => 'wp-config.php', … … 1249 1438 array( 1250 1439 'path' => get_home_path(), 1251 'type' => 'dir',1252 1440 'recommended' => array( '0755', '0750' ), 1253 1441 'label' => __( 'WordPress root directory', 'security-hardener' ), … … 1255 1443 array( 1256 1444 'path' => WP_CONTENT_DIR, 1257 'type' => 'dir',1258 1445 'recommended' => array( '0755', '0750' ), 1259 1446 'label' => 'wp-content', … … 1261 1448 array( 1262 1449 'path' => $upload_dir['basedir'], 1263 'type' => 'dir',1264 1450 'recommended' => array( '0755', '0750' ), 1265 1451 'label' => __( 'Uploads directory', 'security-hardener' ), … … 1273 1459 continue; 1274 1460 } 1275 1276 1461 $perms = substr( sprintf( '%o', fileperms( $check['path'] ) ), -4 ); 1277 1278 1462 if ( ! in_array( $perms, $check['recommended'], true ) ) { 1279 $issues[] = sprintf( 1280 /* translators: 1: file/directory name, 2: current permissions, 3: recommended permissions */ 1281 __( '%1$s has permissions %2$s (recommended: %3$s)', 'security-hardener' ), 1282 '<code>' . esc_html( $check['label'] ) . '</code>', 1283 '<code>' . esc_html( $perms ) . '</code>', 1284 '<code>' . esc_html( implode( ', ', $check['recommended'] ) ) . '</code>' 1463 $issues[] = array( 1464 'label' => $check['label'], 1465 'current' => $perms, 1466 'recommended' => implode( ', ', $check['recommended'] ), 1285 1467 ); 1286 1468 } 1287 1469 } 1288 1289 if ( ! empty( $issues ) ) { 1470 ?> 1471 <hr style="margin:24px 0;"> 1472 <h2><?php esc_html_e( 'File Permissions', 'security-hardener' ); ?></h2> 1473 1474 <?php if ( empty( $issues ) ) : ?> 1475 <div style="display:flex;align-items:center;gap:8px;padding:10px 14px;background:#f0f6f0;border:1px solid #c3c4c7;border-radius:4px;font-size:13px;color:#1d2327;"> 1476 <span style="width:8px;height:8px;border-radius:50%;background:#00a32a;flex-shrink:0;display:inline-block;"></span> 1477 <?php esc_html_e( 'All checked paths have correct file permissions.', 'security-hardener' ); ?> 1478 </div> 1479 <?php else : ?> 1480 <div style="border:1px solid #c3c4c7;border-radius:4px;overflow:hidden;"> 1481 <table class="wp-list-table widefat fixed striped"> 1482 <thead> 1483 <tr> 1484 <th><?php esc_html_e( 'Path', 'security-hardener' ); ?></th> 1485 <th style="width:120px;"><?php esc_html_e( 'Current', 'security-hardener' ); ?></th> 1486 <th style="width:200px;"><?php esc_html_e( 'Recommended', 'security-hardener' ); ?></th> 1487 <th style="width:140px;"><?php esc_html_e( 'Status', 'security-hardener' ); ?></th> 1488 </tr> 1489 </thead> 1490 <tbody> 1491 <?php foreach ( $issues as $issue ) : ?> 1492 <tr> 1493 <td><code><?php echo esc_html( $issue['label'] ); ?></code></td> 1494 <td><code><?php echo esc_html( $issue['current'] ); ?></code></td> 1495 <td><code><?php echo esc_html( $issue['recommended'] ); ?></code></td> 1496 <td> 1497 <span style="display:inline-flex;align-items:center;gap:5px;"> 1498 <span style="width:7px;height:7px;border-radius:50%;background:#d63638;display:inline-block;flex-shrink:0;"></span> 1499 <?php esc_html_e( 'Too permissive', 'security-hardener' ); ?> 1500 </span> 1501 </td> 1502 </tr> 1503 <?php endforeach; ?> 1504 </tbody> 1505 </table> 1506 </div> 1507 <?php endif; ?> 1508 1509 <p style="font-size:12px;color:#646970;margin-top:8px;"> 1510 <?php 1511 printf( 1512 /* translators: %s: URL to file permissions documentation */ 1513 wp_kses_post( __( 'Learn more about <a href="%s" target="_blank">WordPress file permissions</a>.', 'security-hardener' ) ), 1514 'https://developer.wordpress.org/advanced-administration/server/file-permissions/' 1515 ); 1290 1516 ?> 1291 <div class="notice notice-warning"> 1292 <p><strong><?php esc_html_e( 'File Permission Issues Detected:', 'security-hardener' ); ?></strong></p> 1293 <ul style="list-style: disc; padding-left: 20px;"> 1294 <?php foreach ( $issues as $issue ) : ?> 1295 <li><?php echo wp_kses_post( $issue ); ?></li> 1296 <?php endforeach; ?> 1297 </ul> 1298 <p> 1299 <?php 1300 printf( 1301 /* translators: %s: URL to file permissions documentation */ 1302 wp_kses_post( __( 'Learn more about <a href="%s" target="_blank">WordPress file permissions</a>.', 'security-hardener' ) ), 1303 'https://developer.wordpress.org/advanced-administration/server/file-permissions/' 1304 ); 1305 ?> 1306 </p> 1307 </div> 1308 <?php 1309 } 1517 </p> 1518 <?php 1310 1519 } 1311 1520 -
security-hardener/trunk/readme.txt
r3487909 r3492634 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.2 7 Stable tag: 2. 0.27 Stable tag: 2.1.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 163 163 164 164 == Changelog == 165 166 = 2.1.0 - 2026-03-26 = 167 * Added: Interactive hardening checklist — users can mark each recommendation as done. 168 * Improved: File permissions check moved from floating admin notice into the settings page. 169 * Fixed: "Enable preload" description now clarifies it only adds the preload directive to the HSTS header. 170 * Fixed: "Clean wp_head" description no longer uses HTML entities that rendered as literal text in some contexts. 171 * Fixed: wp-includes recommendation text no longer implies a code snippet follows. 165 172 166 173 = 2.0.2 - 2026-03-21 = -
security-hardener/trunk/security-hardener.php
r3487909 r3492634 4 4 Plugin URI: https://wordpress.org/plugins/security-hardener/ 5 5 Description: Basic hardening: secure headers, disable XML-RPC/pingbacks, hide version, block user enumeration, generic login errors, and IP-based rate limiting. 6 Version: 2. 0.26 Version: 2.1.0 7 7 Requires at least: 6.9 8 8 Tested up to: 6.9 … … 20 20 21 21 // Plugin constants 22 define( 'WPSH_VERSION', '2. 0.2' );22 define( 'WPSH_VERSION', '2.1.0' ); 23 23 define( 'WPSH_FILE', __FILE__ ); 24 24 define( 'WPSH_BASENAME', plugin_basename( __FILE__ ) ); … … 35 35 */ 36 36 const OPTION_NAME = 'wpsh_options'; 37 38 /** 39 * Checklist state option name in database 40 */ 41 const CHECKLIST_OPTION = 'wpsh_checklist'; 37 42 38 43 /** … … 140 145 add_action( 'admin_notices', array( $this, 'show_admin_notices' ) ); 141 146 add_filter( 'plugin_action_links_' . WPSH_BASENAME, array( $this, 'add_settings_link' ) ); 147 add_action( 'wp_ajax_wpsh_toggle_checklist', array( $this, 'ajax_toggle_checklist' ) ); 148 add_action( 'wp_ajax_wpsh_reset_checklist', array( $this, 'ajax_reset_checklist' ) ); 142 149 } 143 150 } … … 808 815 .wpsh-recommendations li{margin-bottom:4px;} 809 816 .wpsh-save-bar{margin:20px 0 4px;} 817 .wpsh-cl-header{display:flex;align-items:center;justify-content:space-between;margin-top:24px;} 818 .wpsh-cl-progress{display:flex;align-items:center;gap:8px;} 819 .wpsh-cl-bar{height:6px;width:120px;background:#c3c4c7;border-radius:3px;overflow:hidden;} 820 .wpsh-cl-bar-fill{height:100%;background:#2271b1;border-radius:3px;transition:width .2s;} 821 .wpsh-cl-pct{font-size:12px;color:#646970;white-space:nowrap;} 822 .wpsh-cl-item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid #f0f0f1;cursor:pointer;transition:background .1s;} 823 .wpsh-cl-item:last-child{border-bottom:none;} 824 .wpsh-cl-item:hover{background:#f6f7f7;} 825 .wpsh-cl-item:focus{outline:2px solid #2271b1;outline-offset:-2px;} 826 .wpsh-cl-item.wpsh-cl-done{background:#f0f6f0;} 827 .wpsh-cl-check{flex-shrink:0;width:18px;height:18px;border:1.5px solid #c3c4c7;border-radius:3px;margin-top:1px;display:flex;align-items:center;justify-content:center;transition:all .15s;} 828 .wpsh-cl-item.wpsh-cl-done .wpsh-cl-check{background:#2271b1;border-color:#2271b1;} 829 .wpsh-cl-check svg{display:none;} 830 .wpsh-cl-item.wpsh-cl-done .wpsh-cl-check svg{display:block;} 831 .wpsh-cl-label{font-size:13px;line-height:1.45;color:#1d2327;} 832 .wpsh-cl-item.wpsh-cl-done .wpsh-cl-label{color:#646970;text-decoration:line-through;text-decoration-color:#c3c4c7;} 810 833 @media(max-width:1200px){.wpsh-grid{grid-template-columns:repeat(2,minmax(0,1fr));}} 811 834 @media(max-width:782px){.wpsh-grid{grid-template-columns:minmax(0,1fr);}} … … 884 907 885 908 /** 909 * AJAX handler — toggle a single checklist item. 910 * 911 * Expects POST: nonce, item_id (int 0-13), checked (0|1). 912 */ 913 public function ajax_toggle_checklist(): void { 914 check_ajax_referer( 'wpsh_checklist_nonce', 'nonce' ); 915 916 if ( ! current_user_can( 'manage_options' ) ) { 917 wp_send_json_error( 'Unauthorized', 403 ); 918 } 919 920 $item_id = isset( $_POST['item_id'] ) ? absint( $_POST['item_id'] ) : null; 921 $checked = isset( $_POST['checked'] ) && '1' === $_POST['checked']; 922 923 if ( null === $item_id || $item_id > 13 ) { 924 wp_send_json_error( 'Invalid item', 400 ); 925 } 926 927 $state = get_option( self::CHECKLIST_OPTION, [] ); 928 929 if ( $checked ) { 930 $state[ $item_id ] = true; 931 } else { 932 unset( $state[ $item_id ] ); 933 } 934 935 update_option( self::CHECKLIST_OPTION, $state, false ); 936 wp_send_json_success( [ 'done' => count( $state ), 'total' => 14 ] ); 937 } 938 939 /** 940 * AJAX handler — reset all checklist items. 941 * 942 * Expects POST: nonce. 943 */ 944 public function ajax_reset_checklist(): void { 945 check_ajax_referer( 'wpsh_checklist_nonce', 'nonce' ); 946 947 if ( ! current_user_can( 'manage_options' ) ) { 948 wp_send_json_error( 'Unauthorized', 403 ); 949 } 950 951 update_option( self::CHECKLIST_OPTION, [], false ); 952 wp_send_json_success( [ 'done' => 0, 'total' => 14 ] ); 953 } 954 955 /** 956 * Render the interactive hardening recommendations checklist. 957 * 958 * State is persisted in wpsh_checklist via AJAX (no page reload needed). 959 */ 960 private function render_checklist(): void { 961 $state = get_option( self::CHECKLIST_OPTION, [] ); 962 $done = count( $state ); 963 $total = 14; 964 $pct = $total > 0 ? round( $done / $total * 100 ) : 0; 965 966 $items = [ 967 __( 'Use strong passwords and enable two-factor authentication', 'security-hardener' ), 968 __( 'Keep WordPress, themes, and plugins updated', 'security-hardener' ), 969 __( 'Use HTTPS (SSL/TLS) for your entire site', 'security-hardener' ), 970 __( 'Regular backups stored off-site', 'security-hardener' ), 971 __( 'Limit login attempts at the server/firewall level', 'security-hardener' ), 972 __( 'Use security plugins for malware scanning', 'security-hardener' ), 973 __( 'Restrict file permissions (directories: 755, files: 644)', 'security-hardener' ), 974 __( 'Consider using a Web Application Firewall (WAF)', 'security-hardener' ), 975 __( 'Protect the wp-admin directory with an additional HTTP authentication layer (BasicAuth)', 'security-hardener' ), 976 __( 'Change the default database table prefix from wp_ to a custom value', 'security-hardener' ), 977 __( 'Rename the default admin account to a non-obvious username', 'security-hardener' ), 978 __( 'Restrict database user privileges to SELECT, INSERT, UPDATE and DELETE only', 'security-hardener' ), 979 __( 'Protect wp-config.php by moving it one directory above the WordPress root or restricting access via .htaccess', 'security-hardener' ), 980 __( 'Block direct access to files in wp-includes/ via .htaccess rules — see the WordPress Hardening Guide for the full snippet.', 'security-hardener' ), 981 ]; 982 983 $nonce = wp_create_nonce( 'wpsh_checklist_nonce' ); 984 ?> 985 <hr style="margin: 24px 0;"> 986 987 <div class="wpsh-cl-header"> 988 <h2 style="margin:0;"><?php esc_html_e( 'Additional Hardening Recommendations', 'security-hardener' ); ?></h2> 989 <div class="wpsh-cl-progress"> 990 <div class="wpsh-cl-bar"> 991 <div class="wpsh-cl-bar-fill" id="wpsh-bar-fill" style="width:<?php echo absint( $pct ); ?>%"></div> 992 </div> 993 <span class="wpsh-cl-pct" id="wpsh-cl-pct"> 994 <?php 995 printf( 996 /* translators: 1: completed items, 2: total items */ 997 esc_html__( '%1$d / %2$d done', 'security-hardener' ), 998 absint( $done ), 999 absint( $total ) 1000 ); 1001 ?> 1002 </span> 1003 </div> 1004 </div> 1005 1006 <div class="wpsh-card" style="margin-top:12px;" id="wpsh-checklist"> 1007 <?php foreach ( $items as $id => $label ) : ?> 1008 <div class="wpsh-cl-item<?php echo isset( $state[ $id ] ) ? ' wpsh-cl-done' : ''; ?>" 1009 data-id="<?php echo absint( $id ); ?>" 1010 data-nonce="<?php echo esc_attr( $nonce ); ?>" 1011 role="checkbox" 1012 aria-checked="<?php echo isset( $state[ $id ] ) ? 'true' : 'false'; ?>" 1013 tabindex="0"> 1014 <div class="wpsh-cl-check" aria-hidden="true"> 1015 <svg width="10" height="8" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg"> 1016 <path d="M1 4l3 3 5-6" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/> 1017 </svg> 1018 </div> 1019 <span class="wpsh-cl-label"><?php echo esc_html( $label ); ?></span> 1020 </div> 1021 <?php endforeach; ?> 1022 </div> 1023 1024 <div style="display:flex;justify-content:space-between;align-items:center;margin-top:10px;"> 1025 <p style="margin:0;font-size:12px;color:#646970;"> 1026 <?php esc_html_e( 'Your progress is saved automatically.', 'security-hardener' ); ?> 1027 </p> 1028 <button type="button" id="wpsh-cl-reset" class="button-link" 1029 data-nonce="<?php echo esc_attr( $nonce ); ?>" 1030 style="font-size:12px;color:#2271b1;"> 1031 <?php esc_html_e( 'Reset all', 'security-hardener' ); ?> 1032 </button> 1033 </div> 1034 1035 <p style="margin-top:16px;"> 1036 <?php 1037 printf( 1038 /* translators: %s: URL to WordPress hardening guide */ 1039 wp_kses_post( __( 'For more information, see the official <a href="%s" target="_blank">WordPress Hardening Guide</a>.', 'security-hardener' ) ), 1040 'https://developer.wordpress.org/advanced-administration/security/hardening/' 1041 ); 1042 ?> 1043 </p> 1044 1045 <script> 1046 (function() { 1047 var ajaxUrl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>; 1048 var total = <?php echo absint( $total ); ?>; 1049 1050 function updateProgress( done ) { 1051 var pct = total > 0 ? Math.round( done / total * 100 ) : 0; 1052 document.getElementById( 'wpsh-bar-fill' ).style.width = pct + '%'; 1053 document.getElementById( 'wpsh-cl-pct' ).textContent = done + ' / ' + total + ' done'; 1054 } 1055 1056 function sendToggle( id, checked, nonce ) { 1057 var fd = new FormData(); 1058 fd.append( 'action', 'wpsh_toggle_checklist' ); 1059 fd.append( 'nonce', nonce ); 1060 fd.append( 'item_id', id ); 1061 fd.append( 'checked', checked ? '1' : '0' ); 1062 fetch( ajaxUrl, { method: 'POST', body: fd, credentials: 'same-origin' } ) 1063 .then( function( r ) { return r.json(); } ) 1064 .then( function( data ) { if ( data.success ) updateProgress( data.data.done ); } ); 1065 } 1066 1067 document.querySelectorAll( '.wpsh-cl-item' ).forEach( function( el ) { 1068 function toggle() { 1069 var done = el.classList.toggle( 'wpsh-cl-done' ); 1070 var checked = el.classList.contains( 'wpsh-cl-done' ); 1071 el.setAttribute( 'aria-checked', checked ? 'true' : 'false' ); 1072 sendToggle( el.dataset.id, checked, el.dataset.nonce ); 1073 } 1074 el.addEventListener( 'click', toggle ); 1075 el.addEventListener( 'keydown', function( e ) { 1076 if ( e.key === ' ' || e.key === 'Enter' ) { e.preventDefault(); toggle(); } 1077 } ); 1078 } ); 1079 1080 var resetBtn = document.getElementById( 'wpsh-cl-reset' ); 1081 if ( resetBtn ) { 1082 resetBtn.addEventListener( 'click', function() { 1083 var fd = new FormData(); 1084 fd.append( 'action', 'wpsh_reset_checklist' ); 1085 fd.append( 'nonce', resetBtn.dataset.nonce ); 1086 fetch( ajaxUrl, { method: 'POST', body: fd, credentials: 'same-origin' } ) 1087 .then( function( r ) { return r.json(); } ) 1088 .then( function( data ) { 1089 if ( data.success ) { 1090 document.querySelectorAll( '.wpsh-cl-item' ).forEach( function( el ) { 1091 el.classList.remove( 'wpsh-cl-done' ); 1092 el.setAttribute( 'aria-checked', 'false' ); 1093 } ); 1094 updateProgress( 0 ); 1095 } 1096 } ); 1097 } ); 1098 } 1099 })(); 1100 </script> 1101 <?php 1102 } 1103 1104 /** 886 1105 * Render settings page 887 1106 */ … … 1066 1285 sprintf( 1067 1286 /* translators: %s: URL to HSTS preload list */ 1068 wp_kses_post( __( ' Submit to the <a href="%s" target="_blank">HSTS Preload List</a>. Requires 1 year max-age.', 'security-hardener' ) ),1287 wp_kses_post( __( 'Adds the preload directive to the HSTS header. Required before submitting manually to <a href="%s" target="_blank">hstspreload.org</a>.', 'security-hardener' ) ), 1069 1288 'https://hstspreload.org/' 1070 1289 ) … … 1089 1308 'clean_head', 1090 1309 __( 'Clean wp_head', 'security-hardener' ), 1091 __( 'Removes RSD link, Windows Live Writer manifest, shortlink, and emoji scripts from <head>.', 'security-hardener' )1310 __( 'Removes RSD link, Windows Live Writer manifest, shortlink, and emoji scripts from the page head.', 'security-hardener' ) 1092 1311 ); 1093 1312 $this->render_toggle_row( … … 1116 1335 <?php $this->render_security_logs(); ?> 1117 1336 1118 <hr style="margin: 24px 0;"> 1119 1120 <h2><?php esc_html_e( 'Additional Hardening Recommendations', 'security-hardener' ); ?></h2> 1121 <ul class="wpsh-recommendations" style="list-style: disc; padding-left: 20px;"> 1122 <li><?php esc_html_e( 'Use strong passwords and enable two-factor authentication', 'security-hardener' ); ?></li> 1123 <li><?php esc_html_e( 'Keep WordPress, themes, and plugins updated', 'security-hardener' ); ?></li> 1124 <li><?php esc_html_e( 'Use HTTPS (SSL/TLS) for your entire site', 'security-hardener' ); ?></li> 1125 <li><?php esc_html_e( 'Regular backups stored off-site', 'security-hardener' ); ?></li> 1126 <li><?php esc_html_e( 'Limit login attempts at the server/firewall level', 'security-hardener' ); ?></li> 1127 <li><?php esc_html_e( 'Use security plugins for malware scanning', 'security-hardener' ); ?></li> 1128 <li><?php esc_html_e( 'Restrict file permissions (directories: 755, files: 644)', 'security-hardener' ); ?></li> 1129 <li><?php esc_html_e( 'Consider using a Web Application Firewall (WAF)', 'security-hardener' ); ?></li> 1130 <li><?php esc_html_e( 'Protect the wp-admin directory with an additional HTTP authentication layer (BasicAuth)', 'security-hardener' ); ?></li> 1131 <li><?php esc_html_e( 'Change the default database table prefix from wp_ to a custom value', 'security-hardener' ); ?></li> 1132 <li><?php esc_html_e( 'Rename the default admin account to a non-obvious username', 'security-hardener' ); ?></li> 1133 <li><?php esc_html_e( 'Restrict database user privileges to SELECT, INSERT, UPDATE and DELETE only', 'security-hardener' ); ?></li> 1134 <li><?php esc_html_e( 'Protect wp-config.php by moving it one directory above the WordPress root or restricting access via .htaccess', 'security-hardener' ); ?></li> 1135 <li><?php esc_html_e( 'Block direct access to files in wp-includes/ by adding the following rules to your .htaccess file (outside the WordPress tags).', 'security-hardener' ); ?></li> 1136 </ul> 1137 <p> 1138 <?php 1139 printf( 1140 /* translators: %s: URL to WordPress hardening guide */ 1141 wp_kses_post( __( 'For more information, see the official <a href="%s" target="_blank">WordPress Hardening Guide</a>.', 'security-hardener' ) ), 1142 'https://developer.wordpress.org/advanced-administration/security/hardening/' 1143 ); 1144 ?> 1145 </p> 1337 <?php $this->render_file_permissions(); ?> 1338 1339 <?php $this->render_checklist(); ?> 1146 1340 1147 1341 </div><!-- .wrap --> … … 1221 1415 } 1222 1416 1223 // Check file permissions 1224 $this->check_file_permissions_notice(); 1225 } 1226 1227 /** 1228 * Check file permissions and show notice 1229 */ 1230 private function check_file_permissions_notice(): void { 1231 // Only show on plugin settings page 1232 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; 1233 if ( ! $screen || 'settings_page_security-hardener' !== $screen->id ) { 1234 return; 1235 } 1236 1417 // Check file permissions is now rendered inline in render_settings_page() 1418 } 1419 1420 /** 1421 * Render file permissions check as an inline section on the settings page. 1422 * 1423 * Shows a success notice when all paths are correct, or a table listing 1424 * only the paths with issues when problems are found. 1425 */ 1426 private function render_file_permissions(): void { 1237 1427 if ( ! function_exists( 'get_home_path' ) ) { 1238 1428 require_once ABSPATH . 'wp-admin/includes/file.php'; … … 1243 1433 array( 1244 1434 'path' => ABSPATH . 'wp-config.php', 1245 'type' => 'file',1246 1435 'recommended' => array( '0600', '0640', '0644' ), 1247 1436 'label' => 'wp-config.php', … … 1249 1438 array( 1250 1439 'path' => get_home_path(), 1251 'type' => 'dir',1252 1440 'recommended' => array( '0755', '0750' ), 1253 1441 'label' => __( 'WordPress root directory', 'security-hardener' ), … … 1255 1443 array( 1256 1444 'path' => WP_CONTENT_DIR, 1257 'type' => 'dir',1258 1445 'recommended' => array( '0755', '0750' ), 1259 1446 'label' => 'wp-content', … … 1261 1448 array( 1262 1449 'path' => $upload_dir['basedir'], 1263 'type' => 'dir',1264 1450 'recommended' => array( '0755', '0750' ), 1265 1451 'label' => __( 'Uploads directory', 'security-hardener' ), … … 1273 1459 continue; 1274 1460 } 1275 1276 1461 $perms = substr( sprintf( '%o', fileperms( $check['path'] ) ), -4 ); 1277 1278 1462 if ( ! in_array( $perms, $check['recommended'], true ) ) { 1279 $issues[] = sprintf( 1280 /* translators: 1: file/directory name, 2: current permissions, 3: recommended permissions */ 1281 __( '%1$s has permissions %2$s (recommended: %3$s)', 'security-hardener' ), 1282 '<code>' . esc_html( $check['label'] ) . '</code>', 1283 '<code>' . esc_html( $perms ) . '</code>', 1284 '<code>' . esc_html( implode( ', ', $check['recommended'] ) ) . '</code>' 1463 $issues[] = array( 1464 'label' => $check['label'], 1465 'current' => $perms, 1466 'recommended' => implode( ', ', $check['recommended'] ), 1285 1467 ); 1286 1468 } 1287 1469 } 1288 1289 if ( ! empty( $issues ) ) { 1470 ?> 1471 <hr style="margin:24px 0;"> 1472 <h2><?php esc_html_e( 'File Permissions', 'security-hardener' ); ?></h2> 1473 1474 <?php if ( empty( $issues ) ) : ?> 1475 <div style="display:flex;align-items:center;gap:8px;padding:10px 14px;background:#f0f6f0;border:1px solid #c3c4c7;border-radius:4px;font-size:13px;color:#1d2327;"> 1476 <span style="width:8px;height:8px;border-radius:50%;background:#00a32a;flex-shrink:0;display:inline-block;"></span> 1477 <?php esc_html_e( 'All checked paths have correct file permissions.', 'security-hardener' ); ?> 1478 </div> 1479 <?php else : ?> 1480 <div style="border:1px solid #c3c4c7;border-radius:4px;overflow:hidden;"> 1481 <table class="wp-list-table widefat fixed striped"> 1482 <thead> 1483 <tr> 1484 <th><?php esc_html_e( 'Path', 'security-hardener' ); ?></th> 1485 <th style="width:120px;"><?php esc_html_e( 'Current', 'security-hardener' ); ?></th> 1486 <th style="width:200px;"><?php esc_html_e( 'Recommended', 'security-hardener' ); ?></th> 1487 <th style="width:140px;"><?php esc_html_e( 'Status', 'security-hardener' ); ?></th> 1488 </tr> 1489 </thead> 1490 <tbody> 1491 <?php foreach ( $issues as $issue ) : ?> 1492 <tr> 1493 <td><code><?php echo esc_html( $issue['label'] ); ?></code></td> 1494 <td><code><?php echo esc_html( $issue['current'] ); ?></code></td> 1495 <td><code><?php echo esc_html( $issue['recommended'] ); ?></code></td> 1496 <td> 1497 <span style="display:inline-flex;align-items:center;gap:5px;"> 1498 <span style="width:7px;height:7px;border-radius:50%;background:#d63638;display:inline-block;flex-shrink:0;"></span> 1499 <?php esc_html_e( 'Too permissive', 'security-hardener' ); ?> 1500 </span> 1501 </td> 1502 </tr> 1503 <?php endforeach; ?> 1504 </tbody> 1505 </table> 1506 </div> 1507 <?php endif; ?> 1508 1509 <p style="font-size:12px;color:#646970;margin-top:8px;"> 1510 <?php 1511 printf( 1512 /* translators: %s: URL to file permissions documentation */ 1513 wp_kses_post( __( 'Learn more about <a href="%s" target="_blank">WordPress file permissions</a>.', 'security-hardener' ) ), 1514 'https://developer.wordpress.org/advanced-administration/server/file-permissions/' 1515 ); 1290 1516 ?> 1291 <div class="notice notice-warning"> 1292 <p><strong><?php esc_html_e( 'File Permission Issues Detected:', 'security-hardener' ); ?></strong></p> 1293 <ul style="list-style: disc; padding-left: 20px;"> 1294 <?php foreach ( $issues as $issue ) : ?> 1295 <li><?php echo wp_kses_post( $issue ); ?></li> 1296 <?php endforeach; ?> 1297 </ul> 1298 <p> 1299 <?php 1300 printf( 1301 /* translators: %s: URL to file permissions documentation */ 1302 wp_kses_post( __( 'Learn more about <a href="%s" target="_blank">WordPress file permissions</a>.', 'security-hardener' ) ), 1303 'https://developer.wordpress.org/advanced-administration/server/file-permissions/' 1304 ); 1305 ?> 1306 </p> 1307 </div> 1308 <?php 1309 } 1517 </p> 1518 <?php 1310 1519 } 1311 1520 -
security-hardener/trunk/uninstall.php
r3474999 r3492634 32 32 delete_option( 'wpsh_security_logs' ); 33 33 34 // Delete hardening checklist state. 35 delete_option( 'wpsh_checklist' ); 36 34 37 // Delete login rate-limiting transients. 35 38 // We cannot enumerate every hashed IP key ahead of time, so we retrieve -
security-hardener/uninstall.php
r3487781 r3492634 32 32 delete_option( 'wpsh_security_logs' ); 33 33 34 // Delete hardening checklist state. 35 delete_option( 'wpsh_checklist' ); 36 34 37 // Delete login rate-limiting transients. 35 38 // We cannot enumerate every hashed IP key ahead of time, so we retrieve
Note: See TracChangeset
for help on using the changeset viewer.