Changeset 3682123
- Timestamp:
- 09/05/2026 05:52:11 AM (7 weeks ago)
- Location:
- site-security-auditor
- Files:
-
- 17 added
- 8 edited
- 1 copied
-
assets/banner-1544x500.png (added)
-
assets/banner-772x250.png (modified) ( previous)
-
assets/icon-128x128.png (added)
-
assets/icon-256x256.png (modified) ( previous)
-
assets/screenshot-1.png (added)
-
assets/screenshot-2.png (added)
-
assets/screenshot-3.png (added)
-
tags/1.4.0 (copied) (copied from site-security-auditor/trunk )
-
tags/1.4.0/assets (added)
-
tags/1.4.0/assets/admin.css (added)
-
tags/1.4.0/assets/icon.png (added)
-
tags/1.4.0/includes (added)
-
tags/1.4.0/includes/class-scanner.php (added)
-
tags/1.4.0/includes/dashboard.php (added)
-
tags/1.4.0/languages/site-security-auditor.pot (modified) (1 diff)
-
tags/1.4.0/readme.txt (modified) (1 diff)
-
tags/1.4.0/site-security-auditor.php (modified) (1 diff)
-
trunk/assets (added)
-
trunk/assets/admin.css (added)
-
trunk/assets/icon.png (added)
-
trunk/includes (added)
-
trunk/includes/class-scanner.php (added)
-
trunk/includes/dashboard.php (added)
-
trunk/languages/site-security-auditor.pot (modified) (1 diff)
-
trunk/readme.txt (modified) (1 diff)
-
trunk/site-security-auditor.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
site-security-auditor/tags/1.4.0/languages/site-security-auditor.pot
r3365444 r3682123 1 # POT placeholder 1 # Copyright (C) 2026 Gopal Bogati 2 # This file is distributed under the GPL-2.0-or-later. 3 msgid "" 4 msgstr "" 5 "Project-Id-Version: Site Security Auditor 1.4.0\n" 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/trunk\n" 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 8 "Language-Team: LANGUAGE <LL@li.org>\n" 9 "MIME-Version: 1.0\n" 10 "Content-Type: text/plain; charset=UTF-8\n" 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026-09-05T05:49:37+00:00\n" 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.12.0\n" 15 "X-Domain: site-security-auditor\n" 16 17 #. Plugin Name of the plugin 18 #: site-security-auditor.php 19 msgid "Site Security Auditor" 20 msgstr "" 21 22 #. Description of the plugin 23 #: site-security-auditor.php 24 msgid "Review plugin updates, compare plugin and theme files, and follow a clear WordPress security checklist." 25 msgstr "" 26 27 #. Author of the plugin 28 #: site-security-auditor.php 29 msgid "Gopal Bogati" 30 msgstr "" 31 32 #. Author URI of the plugin 33 #: site-security-auditor.php 34 msgid "https://profiles.wordpress.org/gopalbogati/" 35 msgstr "" 36 37 #: includes/dashboard.php:8 38 msgid "SITE SECURITY AUDITOR" 39 msgstr "" 40 41 #: includes/dashboard.php:9 42 msgid "Your security review" 43 msgstr "" 44 45 #: includes/dashboard.php:10 46 msgid "Know what changed. Understand your next step." 47 msgstr "" 48 49 #: includes/dashboard.php:13 50 msgid "Plugin updates available" 51 msgstr "" 52 53 #: includes/dashboard.php:13 54 msgid "Review plugin information" 55 msgstr "" 56 57 #: includes/dashboard.php:14 58 msgid "Settings to review" 59 msgstr "" 60 61 #: includes/dashboard.php:14 62 msgid "Open your checklist" 63 msgstr "" 64 65 #: includes/dashboard.php:15 66 msgid "File comparison" 67 msgstr "" 68 69 #: includes/dashboard.php:15 70 msgid "Baseline ready" 71 msgstr "" 72 73 #: includes/dashboard.php:15 74 msgid "Set up first" 75 msgstr "" 76 77 #: includes/dashboard.php:15 78 msgid "Review files" 79 msgstr "" 80 81 #: includes/dashboard.php:17 82 msgid "These checks reduce guesswork, not all security risks. This is not a malware scanner, firewall, or vulnerability database." 83 msgstr "" 84 85 #: includes/dashboard.php:19 86 msgid "1. Check for file changes" 87 msgstr "" 88 89 #: includes/dashboard.php:20 90 msgid "A baseline is a snapshot of files you trust. Compare against it after maintenance or whenever you want to investigate unexpected changes." 91 msgstr "" 92 93 #: includes/dashboard.php:22 94 msgid "Back up your site and confirm its files are trustworthy." 95 msgstr "" 96 97 #: includes/dashboard.php:23 98 msgid "Save a baseline, then run a comparison when you return." 99 msgstr "" 100 101 #: includes/dashboard.php:24 102 msgid "Review changes with your update history or developer before replacing the baseline." 103 msgstr "" 104 105 #: includes/dashboard.php:27 106 msgid "Baseline saved:" 107 msgstr "" 108 109 #: includes/dashboard.php:29 110 msgid "Compare files now" 111 msgstr "" 112 113 #: includes/dashboard.php:31 114 msgid "Replace the trusted baseline" 115 msgstr "" 116 117 #: includes/dashboard.php:31 118 msgid "Create your first baseline" 119 msgstr "" 120 121 #: includes/dashboard.php:32 122 msgid "Saving a snapshot does not prove that files are clean. Replacing it accepts all current changes as your new reference." 123 msgstr "" 124 125 #: includes/dashboard.php:33 126 msgid "Save trusted baseline" 127 msgstr "" 128 129 #: includes/dashboard.php:36 130 #: includes/dashboard.php:37 131 msgid "Latest file report" 132 msgstr "" 133 134 #. translators: %d: number of files read. 135 #: includes/dashboard.php:39 136 #, php-format 137 msgid "%d files read" 138 msgstr "" 139 140 #: includes/dashboard.php:41 141 msgid "Incomplete — no comparison or baseline replacement was made." 142 msgstr "" 143 144 #: includes/dashboard.php:42 145 msgid "Finish any updates, check file permissions with your host, and retry. Large sites may need a host-level integrity tool." 146 msgstr "" 147 148 #: includes/dashboard.php:45 149 msgid "Baseline saved. Your next comparison will use this snapshot." 150 msgstr "" 151 152 #: includes/dashboard.php:47 153 msgid "Changes are not necessarily malicious: updates and intentional edits also change files. No changes does not guarantee a secure site." 154 msgstr "" 155 156 #: includes/dashboard.php:48 157 msgid "Added" 158 msgstr "" 159 160 #: includes/dashboard.php:48 161 msgid "Modified" 162 msgstr "" 163 164 #: includes/dashboard.php:48 165 msgid "Removed" 166 msgstr "" 167 168 #: includes/dashboard.php:53 169 msgid "Showing the first 100 paths. Download the full report below." 170 msgstr "" 171 172 #: includes/dashboard.php:57 173 msgid "Download file report (JSON)" 174 msgstr "" 175 176 #: includes/dashboard.php:58 177 msgid "The download includes relative file paths. Share only with people you trust." 178 msgstr "" 179 180 #: includes/dashboard.php:61 181 msgid "What is included in a scan?" 182 msgstr "" 183 184 #: includes/dashboard.php:62 185 msgid "Plugin and registered theme directories: PHP, PHP7, PHTML, INC, TWIG, JSON, JS and CSS files. WordPress core, uploads, must-use plugins, database content and other file types are excluded. Symbolic links are not followed and make a scan incomplete." 186 msgstr "" 187 188 #: includes/dashboard.php:63 189 msgid "Limits: 20,000 directory entries, 100 MiB total, 8 MiB per file and an approximately 8-second processing budget. An incomplete scan never replaces your baseline. Files should not be edited while a scan runs." 190 msgstr "" 191 192 #: includes/dashboard.php:64 193 msgid "Your older baseline excludes JS and CSS. Comparisons keep that scope until you save a new trusted baseline." 194 msgstr "" 195 196 #: includes/dashboard.php:68 197 msgid "2. Review your plugins" 198 msgstr "" 199 200 #: includes/dashboard.php:69 201 msgid "Uses the update information already stored by WordPress. Unknown means information is missing or more than two days old; premium plugins may use their own update systems." 202 msgstr "" 203 204 #: includes/dashboard.php:70 205 msgid "Open WordPress Updates" 206 msgstr "" 207 208 #: includes/dashboard.php:71 209 msgid "Manage plugins" 210 msgstr "" 211 212 #: includes/dashboard.php:72 213 msgid "Plugin maintenance table" 214 msgstr "" 215 216 #: includes/dashboard.php:74 217 msgid "Plugin" 218 msgstr "" 219 220 #: includes/dashboard.php:74 221 msgid "Status" 222 msgstr "" 223 224 #: includes/dashboard.php:74 225 msgid "Update information" 226 msgstr "" 227 228 #: includes/dashboard.php:74 229 msgid "Next step" 230 msgstr "" 231 232 #: includes/dashboard.php:78 233 msgid "Active" 234 msgstr "" 235 236 #: includes/dashboard.php:78 237 msgid "Inactive" 238 msgstr "" 239 240 #: includes/dashboard.php:79 241 msgid "Update available" 242 msgstr "" 243 244 #: includes/dashboard.php:79 245 msgid "Unknown — refresh in Updates" 246 msgstr "" 247 248 #: includes/dashboard.php:79 249 msgid "No update reported" 250 msgstr "" 251 252 #: includes/dashboard.php:80 253 msgid "Back up, review the release notes, then update." 254 msgstr "" 255 256 #: includes/dashboard.php:80 257 msgid "Remove it if no longer needed, after checking dependencies." 258 msgstr "" 259 260 #: includes/dashboard.php:80 261 msgid "Keep maintained and review auto-update settings." 262 msgstr "" 263 264 #: includes/dashboard.php:81 265 msgid "Auto-updates selected in WordPress; host or plugin policy may override this." 266 msgstr "" 267 268 #: includes/dashboard.php:84 269 msgid "No update reported is not a security verdict. This tool does not check a vulnerability database or certify plugins as safe." 270 msgstr "" 271 272 #: includes/dashboard.php:87 273 msgid "3. Follow your security checklist" 274 msgstr "" 275 276 #: includes/dashboard.php:89 277 msgid "Configured" 278 msgstr "" 279 280 #: includes/dashboard.php:89 281 msgid "Review" 282 msgstr "" 283 284 #: includes/dashboard.php:92 285 msgid "Also check these with your host or administrator" 286 msgstr "" 287 288 #: includes/dashboard.php:93 289 msgid "Test your backups, use unique passwords and two-factor authentication, remove unused accounts, and keep WordPress and themes updated. These are manual checks; this plugin does not verify them." 290 msgstr "" 291 292 #: includes/dashboard.php:94 293 msgid "Open WordPress Site Health" 294 msgstr "" 295 296 #: includes/dashboard.php:95 297 msgid "Read the official security guide" 298 msgstr "" 299 300 #: includes/dashboard.php:97 301 msgid "Read-only checks. No automatic file edits, tracking, or additional remote metadata requests. Only the latest file report is stored." 302 msgstr "" 303 304 #: site-security-auditor.php:36 305 msgid "Security Audit" 306 msgstr "" 307 308 #: site-security-auditor.php:45 309 msgid "You are not allowed to perform this action." 310 msgstr "" 311 312 #: site-security-auditor.php:66 313 msgid "Run a file check first." 314 msgstr "" 315 316 #: site-security-auditor.php:95 317 msgid "Create a trusted baseline before comparing files." 318 msgstr "" 319 320 #: site-security-auditor.php:139 321 msgid "Encrypted website address" 322 msgstr "" 323 324 #: site-security-auditor.php:139 325 msgid "Use HTTPS for your public site. Ask your host to configure a certificate and redirects." 326 msgstr "" 327 328 #: site-security-auditor.php:140 329 msgid "HTTPS required for administration" 330 msgstr "" 331 332 #: site-security-auditor.php:140 333 msgid "Ask your host to check FORCE_SSL_ADMIN and your HTTPS proxy configuration." 334 msgstr "" 335 336 #: site-security-auditor.php:141 337 msgid "Dashboard code editing disabled" 338 msgstr "" 339 340 #: site-security-auditor.php:141 341 msgid "Consider DISALLOW_FILE_EDIT in wp-config.php. Back up the file and ask your developer before editing it." 342 msgstr "" 343 344 #: site-security-auditor.php:142 345 msgid "WordPress debug display off" 346 msgstr "" 347 348 #: site-security-auditor.php:142 349 msgid "On production sites, keep WordPress debug messages out of public pages. Server-level error display needs a separate host check." 350 msgstr "" 351 352 #: site-security-auditor.php:160 353 msgid "I have reviewed this site and trust its current files. This replaces the previous baseline." 354 msgstr "" -
site-security-auditor/tags/1.4.0/readme.txt
r3365450 r3682123 1 === Site Security Auditor ===1 === Site Security Auditor – Plugin & File Checks === 2 2 Contributors: gopalbogati 3 Tags: security audit, file integrity, plugin updates, security checklist, site health 4 Requires at least: 5.8 5 Tested up to: 7.0 3 6 Requires PHP: 7.4 4 Tested up to: 6.8 5 Stable tag: 1.0 7 Stable tag: 1.4.0 6 8 License: GPLv2 or later 7 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 8 10 9 A simple admin tool to audit plugins, file integrity (baseline + changes), and hardening settings. No tracking or updater hooks.11 Review plugin updates, detect plugin and theme file changes, and follow a clear WordPress security checklist. No account required. 10 12 11 13 == Description == 12 Site Security Auditor helps admins spot risks from outdated/inactive plugins, monitor file changes in plugins/themes, and review hardening settings like DISALLOW_FILE_EDIT or FORCE_SSL_ADMIN. It uses only WordPress core APIs and stores data in options. 14 15 Site Security Auditor gives WordPress administrators a practical security review dashboard. See plugin update information, compare plugin and theme files against a trusted snapshot, and understand which settings deserve attention. 16 17 Open **Tools → Security Audit** to start. Each section explains what it checks, what the result means, and what to do next. It is designed for routine maintenance and investigating unexpected file changes, without automatically editing your site. 18 19 = What you can do = 20 21 * **Review plugin updates:** see available updates, inactive plugins and missing or stale update information. Open the familiar WordPress update screen to take action. 22 * **Compare files:** save a trusted SHA-256 baseline, then identify added, modified and removed code files in plugin and theme directories. 23 * **Return to your latest report:** the most recent file check stays visible with its timestamp and file count. Simply opening the dashboard does not start another scan. 24 * **Download a file report:** export the latest result as JSON to review with your developer or host. 25 * **Follow a plain-language checklist:** review HTTPS configuration, dashboard code editing and WordPress debug display, with links to Site Health and official guidance. 26 * **Use a responsive dashboard:** clear sections, keyboard-accessible controls and a scrollable plugin table on small screens. 27 28 = A useful maintenance routine = 29 30 1. Review available updates and take a backup. 31 2. Confirm your files are trustworthy before saving the first baseline. 32 3. Run a comparison after maintenance or when investigating changes. 33 4. Match changed files to intentional updates or edits. 34 5. Replace the baseline only after reviewing and trusting the current files. 35 36 A baseline records the current state; it does not prove that state is clean. Keep independent backups and ask a trusted professional about unexplained changes. 37 38 = Scope and limitations = 39 40 New baselines cover PHP, PHP7, PHTML, INC, TWIG, JSON, JS and CSS files inside the configured plugin directory and registered theme directories. WordPress core, uploads, must-use plugins, database content and other file types are excluded. Symbolic links are not followed and cause an incomplete result. 41 42 Each run is bounded to 20,000 directory entries, 100 MiB of file data, 8 MiB per file and an approximately 8-second processing budget. Unreadable files or exceeded limits produce an incomplete report. Incomplete scans never replace a baseline or present a partial comparison as complete. Large sites may need a host-level integrity tool. Avoid editing files or running updates during a scan. 43 44 This plugin is **not a malware scanner, firewall or vulnerability database**. It does not repair files, guarantee security, or certify a plugin as safe. An update or legitimate edit can change a file; unchanged files can still contain problems. 45 46 = Privacy and performance = 47 48 No account, API key, telemetry or additional remote metadata requests are required. The dashboard uses the update information already maintained by WordPress; WordPress itself may contact its normal update services. 49 50 File hashes, relative paths, baseline time and the latest file report are stored in the site's options, without autoloading them. Reports are restricted to administrators; on multisite they require a network administrator. Downloaded reports contain relative file paths, so share them only with trusted people. Deactivation and deletion preserve these records to avoid silently discarding your trusted reference. 13 51 14 52 == Installation == 15 1. Upload the ZIP via Plugins → Add New → Upload Plugin16 2. Activate17 3. Go to Tools → Security Audit18 4. Create/Update Baseline → Scan Now19 53 20 == Changeling == 54 1. Install Site Security Auditor from Plugins → Add New, or upload the plugin ZIP. 55 2. Activate the plugin. 56 3. Open Tools → Security Audit using an administrator account. 57 4. Read the baseline warning, confirm that you trust the current files, and choose Save trusted baseline. 58 5. Return and select Compare files now to review changes. 59 60 On multisite, a network administrator can open the dashboard from a site's Tools menu. Baselines and reports are stored per site, while plugin and theme files may be shared across the network. 61 62 == Frequently Asked Questions == 63 64 = Does it scan for malware or known vulnerabilities? = 65 66 No. It compares code-file hashes with your own baseline and shows maintenance/configuration checks. Use other appropriate controls and professional advice alongside this tool. 67 68 = When should I replace the baseline? = 69 70 Only after verifying the current files, for example after reviewing an intentional plugin update. Replacing it accepts the current state as the reference and removes the previous comparison point. 71 72 = Why does a plugin say Unknown? = 73 74 WordPress has not supplied current update information for it, or that information is more than two days old. Open WordPress Updates to refresh it. Some premium or custom plugins use separate update systems. Unknown does not mean unsafe or safe. 75 76 = What happens to my existing baseline when I update? = 77 78 Your baseline is retained. Baselines made before 1.4.0 continue comparing their original file types, without JS and CSS. A newly saved trusted baseline includes JS and CSS. Review changes before replacing it. 79 80 = Why is a scan incomplete? = 81 82 The scan reached a resource limit, encountered a symbolic link, could not read a directory/file, or found a file changing during the run. The report explains the issue. Finish updates and ask your host to check permissions; large sites may need another integrity tool. 83 84 = Does it change my security settings automatically? = 85 86 No. It provides guidance and links. Configuration changes can affect hosting or integrations, so review them with your administrator. 87 88 = Can I use this every day? = 89 90 You can manually review the dashboard and compare files whenever useful. There are no scheduled scans or email alerts in this version. The dashboard stores only the latest file report; download reports you need to keep. 91 92 == Screenshots == 93 94 1. Security review dashboard with maintenance summaries and the trusted-baseline workflow. 95 2. Saved file comparison with added, modified and removed file groups. 96 3. Plugin maintenance information and a plain-language configuration checklist. 97 98 == Changelog == 99 100 = 1.4.0 = 101 * New responsive dashboard, guided baseline workflow and actionable security checklist. 102 * Save the latest file report and download it as JSON. 103 * Run scans only through authenticated, nonce-protected POST actions. 104 * Preserve baselines when scans are incomplete; add resource limits and explicit coverage warnings. 105 * Include JS and CSS in new baselines while preserving legacy comparison scope. 106 * Use cached WordPress update data instead of per-plugin remote lookups on dashboard views. 107 * Recognize network-active plugins and restrict multisite access to network administrators. 108 * Refresh directory branding, documentation, screenshots and release metadata. 109 21 110 = 1.0 = 22 * Add full dashboard sections and escaping 23 * Fix text domain packaging 111 * Initial dashboard for plugin information, file baselines and hardening checks. 112 113 == Upgrade Notice == 114 115 = 1.4.0 = 116 New dashboard and safer file checks. Existing baselines remain intact; newly saved baselines also include JS and CSS. Review changes before replacing a baseline. 117 -
site-security-auditor/tags/1.4.0/site-security-auditor.php
r3365444 r3682123 1 1 <?php 2 2 /** 3 * Plugin Name: Site Security Auditor4 * Description: Audits plugins, file integrity, and hardening to improve site security.5 * Version: 1.3.36 * Author: Gopal Bogati7 * Author URI: https://wordpress.org/8 * Text Domain: site-security-auditor9 * Domain Path: /languages3 * Plugin Name: Site Security Auditor 4 * Description: Review plugin updates, compare plugin and theme files, and follow a clear WordPress security checklist. 5 * Version: 1.4.0 6 * Author: Gopal Bogati 7 * Author URI: https://profiles.wordpress.org/gopalbogati/ 8 * Text Domain: site-security-auditor 9 * Domain Path: /languages 10 10 * Requires at least: 5.8 11 * Tested up to: 6.8 12 * Requires PHP: 7.4 13 * License: GPL-2.0+ 11 * Requires PHP: 7.4 12 * License: GPL-2.0-or-later 14 13 */ 14 if ( ! defined( 'ABSPATH' ) ) { exit; } 15 15 define( 'SITESEAU_PLUGIN_FILE', __FILE__ ); 16 16 define( 'SITESEAU_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 17 17 define( 'SITESEAU_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 18 require_once SITESEAU_PLUGIN_DIR . 'includes/class-scanner.php'; 18 19 20 class Siteseau_Site_Security_Auditor { 21 const OPT_HASHES = 'siteseau_hashes'; 22 const OPT_HASHES_TIME = 'siteseau_hashes_time'; 23 const OPT_REPORT = 'siteseau_report'; 19 24 20 if ( ! defined( 'ABSPATH' ) ) {21 exit;22 }23 24 if ( ! class_exists( 'Siteseau_Site_Security_Auditor' ) ) :25 26 /**27 * Main plugin class.28 */29 class Siteseau_Site_Security_Auditor {30 31 const OPT_HASHES = 'siteseau_hashes';32 const OPT_HASHES_TIME = 'siteseau_hashes_time';33 34 /**35 * Bootstrap.36 */37 25 public static function init() { 38 26 add_action( 'admin_menu', array( __CLASS__, 'add_menu' ) ); 27 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'assets' ) ); 39 28 add_action( 'admin_post_ssa_make_baseline', array( __CLASS__, 'handle_baseline' ) ); 40 29 add_action( 'admin_post_ssa_run_scan', array( __CLASS__, 'handle_scan' ) ); 30 add_action( 'admin_post_ssa_export', array( __CLASS__, 'handle_export' ) ); 41 31 } 42 43 /** 44 * Add Tools -> Security Audit page. 45 */ 32 public static function capability() { 33 return is_multisite() ? 'manage_network_options' : 'manage_options'; 34 } 46 35 public static function add_menu() { 47 add_management_page( 48 __( 'Security Audit', 'site-security-auditor' ), 49 __( 'Security Audit', 'site-security-auditor' ), 50 'manage_options', 51 'site-security-auditor', 52 array( __CLASS__, 'render_page' ) 53 ); 36 add_management_page( __( 'Security Audit', 'site-security-auditor' ), __( 'Security Audit', 'site-security-auditor' ), self::capability(), 'site-security-auditor', array( __CLASS__, 'render_page' ) ); 54 37 } 55 56 /** 57 * Create/update baseline handler. 58 */ 38 public static function assets( $hook ) { 39 if ( 'tools_page_site-security-auditor' === $hook ) { 40 wp_enqueue_style( 'siteseau-admin', SITESEAU_PLUGIN_URL . 'assets/admin.css', array(), '1.4.0' ); 41 } 42 } 43 private static function authorize( $action ) { 44 if ( ! current_user_can( self::capability() ) || 'POST' !== ( isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : '' ) ) { 45 wp_die( esc_html__( 'You are not allowed to perform this action.', 'site-security-auditor' ), '', array( 'response' => 403 ) ); 46 } 47 check_admin_referer( $action ); 48 } 59 49 public static function handle_baseline() { 60 if ( ! current_user_can( 'manage_options' ) ) {61 wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'site-security-auditor' ));62 }63 check_admin_referer( 'ssa_make_baseline' );64 65 $paths = self::get_scan_paths();66 $hashes = self::hash_paths( $paths);67 update_option( self::OPT_HASHES, $hashes, false);68 update_option( self::OPT_HASHES_TIME, time(), false );69 70 wp_safe_redirect( ad d_query_arg( array( 'page' => 'site-security-auditor', 'ssa_msg' => 'baseline_done' ), admin_url( 'tools.php' )) );50 self::authorize( 'ssa_make_baseline' ); 51 self::run_audit( true ); 52 self::redirect(); 53 } 54 public static function handle_scan() { 55 self::authorize( 'ssa_run_scan' ); 56 self::run_audit( false ); 57 self::redirect(); 58 } 59 private static function redirect() { 60 wp_safe_redirect( admin_url( 'tools.php?page=site-security-auditor' ) ); 71 61 exit; 72 62 } 73 74 /** 75 * Run scan handler. 76 */ 77 public static function handle_scan() { 78 if ( ! current_user_can( 'manage_options' ) ) { 79 wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'site-security-auditor' ) ); 80 } 81 check_admin_referer( 'ssa_run_scan' ); 82 83 wp_safe_redirect( add_query_arg( array( 'page' => 'site-security-auditor', 'ssa_scan' => '1' ), admin_url( 'tools.php' ) ) ); 63 public static function handle_export() { 64 self::authorize( 'ssa_export' ); 65 $report = get_option( self::OPT_REPORT, array() ); 66 if ( ! $report ) { wp_die( esc_html__( 'Run a file check first.', 'site-security-auditor' ) ); } 67 nocache_headers(); 68 header( 'Content-Type: application/json; charset=utf-8' ); 69 header( 'Content-Disposition: attachment; filename="site-security-auditor-report.json"' ); 70 echo wp_json_encode( $report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); 84 71 exit; 85 72 } 86 87 /** 88 * Render admin page. 89 */ 90 public static function render_page() { 91 if ( ! current_user_can( 'manage_options' ) ) { 92 return; 73 protected static function scan_roots() { 74 global $wp_theme_directories; 75 $roots = array( 'wp-content/plugins' => WP_PLUGIN_DIR ); 76 $themes = (array) $wp_theme_directories; 77 if ( ! $themes ) { $themes = array( WP_CONTENT_DIR . '/themes' ); } 78 foreach ( array_values( array_unique( $themes ) ) as $i => $path ) { 79 $label = wp_normalize_path( $path ) === wp_normalize_path( WP_CONTENT_DIR . '/themes' ) ? 'wp-content/themes' : 'theme-root-' . $i; 80 $roots[ $label ] = $path; 93 81 } 94 95 $plugins_info = self::collect_plugins_info(); 96 $hardening = self::hardening_checks(); 97 $baseline_ts = intval( get_option( self::OPT_HASHES_TIME, 0 ) ); 98 $hashes = get_option( self::OPT_HASHES, array() ); 99 $scan_results = array(); 100 101 if ( isset( $_GET['ssa_scan'] ) && '1' === $_GET['ssa_scan'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended 102 $scan_results = self::compare_hashes( is_array( $hashes ) ? $hashes : array(), self::hash_paths( self::get_scan_paths() ) ); 103 } 104 105 echo '<div class="wrap">'; 106 echo '<h1>' . esc_html__( 'Security Audit Dashboard', 'site-security-auditor' ) . '</h1>'; 107 108 if ( isset( $_GET['ssa_msg'] ) && 'baseline_done' === $_GET['ssa_msg'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended 109 echo '<div class="notice notice-success"><p>' . esc_html__( 'File integrity baseline created/updated.', 'site-security-auditor' ) . '</p></div>'; 110 } 111 112 // Plugins Risk Overview. 113 echo '<h2>' . esc_html__( 'Plugins Risk Overview', 'site-security-auditor' ) . '</h2>'; 114 echo '<p>' . esc_html__( 'Flags plugins with updates available, last updated over a year ago, or inactive status.', 'site-security-auditor' ) . '</p>'; 115 echo '<table class="widefat striped"><thead><tr>'; 116 echo '<th>' . esc_html__( 'Plugin', 'site-security-auditor' ) . '</th>'; 117 echo '<th>' . esc_html__( 'Version', 'site-security-auditor' ) . '</th>'; 118 echo '<th>' . esc_html__( 'Status', 'site-security-auditor' ) . '</th>'; 119 echo '<th>' . esc_html__( 'Update Available', 'site-security-auditor' ) . '</th>'; 120 echo '<th>' . esc_html__( 'Last Updated', 'site-security-auditor' ) . '</th>'; 121 echo '<th>' . esc_html__( 'Risk', 'site-security-auditor' ) . '</th>'; 122 echo '</tr></thead><tbody>'; 123 124 if ( empty( $plugins_info ) ) { 125 echo '<tr><td colspan="6">' . esc_html__( 'No plugins found.', 'site-security-auditor' ) . '</td></tr>'; 126 } else { 127 foreach ( $plugins_info as $row ) { 128 $risk_labels = array(); 129 if ( ! empty( $row['update_available'] ) ) { 130 $risk_labels[] = __( 'Update available', 'site-security-auditor' ); 131 } 132 if ( ! empty( $row['stale'] ) ) { 133 $risk_labels[] = __( 'Stale (> 1 year)', 'site-security-auditor' ); 134 } 135 if ( empty( $row['active'] ) ) { 136 $risk_labels[] = __( 'Inactive', 'site-security-auditor' ); 137 } 138 echo '<tr>'; 139 echo '<td>' . esc_html( $row['name'] ) . '</td>'; 140 echo '<td>' . esc_html( $row['version'] ) . '</td>'; 141 echo '<td>' . ( ! empty( $row['active'] ) ? esc_html__( 'Active', 'site-security-auditor' ) : esc_html__( 'Inactive', 'site-security-auditor' ) ) . '</td>'; 142 echo '<td>' . ( ! empty( $row['update_available'] ) ? esc_html__( 'Yes', 'site-security-auditor' ) : esc_html__( 'No', 'site-security-auditor' ) ) . '</td>'; 143 echo '<td>' . esc_html( $row['last_updated'] ) . '</td>'; 144 echo '<td>' . ( $risk_labels ? esc_html( implode( ', ', $risk_labels ) ) : esc_html__( 'OK', 'site-security-auditor' ) ) . '</td>'; 145 echo '</tr>'; 146 } 147 } 148 echo '</tbody></table>'; 149 150 // File Integrity Monitor. 151 echo '<hr /><h2>' . esc_html__( 'File Integrity Monitor', 'site-security-auditor' ) . '</h2>'; 152 echo '<p>' . esc_html__( 'Create a hash baseline for plugins/themes and scan to detect added, modified or removed files.', 'site-security-auditor' ) . '</p>'; 153 154 if ( $baseline_ts ) { 155 /* translators: %s: date/time. */ 156 $baseline_text = sprintf( esc_html__( 'Baseline last updated: %s', 'site-security-auditor' ), wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $baseline_ts ) ); 157 echo '<p>' . esc_html( $baseline_text ) . '</p>'; 158 } else { 159 echo '<p>' . esc_html__( 'No baseline yet.', 'site-security-auditor' ) . '</p>'; 160 } 161 162 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" style="display:inline-block;margin-right:10px;">'; 163 wp_nonce_field( 'ssa_make_baseline' ); 164 echo '<input type="hidden" name="action" value="ssa_make_baseline" />'; 165 echo '<button type="submit" class="button button-primary" onclick="return confirm(\'' . esc_js( __( 'Create/Update baseline now?', 'site-security-auditor' ) ) . '\');">' . esc_html__( 'Create/Update Baseline', 'site-security-auditor' ) . '</button>'; 166 echo '</form>'; 167 168 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" style="display:inline-block;">'; 169 wp_nonce_field( 'ssa_run_scan' ); 170 echo '<input type="hidden" name="action" value="ssa_run_scan" />'; 171 echo '<button type="submit" class="button">' . esc_html__( 'Scan Now', 'site-security-auditor' ) . '</button>'; 172 echo '</form>'; 173 174 if ( ! empty( $scan_results ) ) { 175 echo '<h3 style="margin-top:20px;">' . esc_html__( 'Scan Results', 'site-security-auditor' ) . '</h3>'; 176 echo '<table class="widefat striped"><thead><tr><th>' . esc_html__( 'Change', 'site-security-auditor' ) . '</th><th>' . esc_html__( 'File', 'site-security-auditor' ) . '</th></tr></thead><tbody>'; 177 $printed = false; 178 foreach ( array( 'added', 'modified', 'removed' ) as $key ) { 179 if ( ! empty( $scan_results[ $key ] ) ) { 180 foreach ( $scan_results[ $key ] as $file ) { 181 echo '<tr><td>' . esc_html( ucfirst( $key ) ) . '</td><td>' . esc_html( $file ) . '</td></tr>'; 182 $printed = true; 82 return $roots; 83 } 84 /** Protected operation, invoked only by the authorized POST handlers. */ 85 protected static function run_audit( $make_baseline ) { 86 $lock = (int) get_option( 'siteseau_scan_lock', 0 ); 87 if ( $lock && $lock < time() - 300 ) { delete_option( 'siteseau_scan_lock' ); } 88 if ( ! add_option( 'siteseau_scan_lock', time(), '', false ) ) { return; } 89 try { 90 $baseline_time = (int) get_option( self::OPT_HASHES_TIME, 0 ); 91 $old = get_option( self::OPT_HASHES, array() ); 92 $scope = $make_baseline ? 2 : (int) get_option( 'siteseau_scope', 1 ); 93 $report = array( 'time' => time(), 'baseline_time' => $baseline_time, 'scope' => $scope, 'kind' => $make_baseline ? 'baseline' : 'comparison', 'complete' => false, 'files' => 0, 'issues' => array(), 'changes' => array() ); 94 if ( ! $make_baseline && ( ! $baseline_time || ! is_array( $old ) ) ) { 95 $report['issues'][] = __( 'Create a trusted baseline before comparing files.', 'site-security-auditor' ); 96 } else { 97 $limits = 1 === $scope ? array( 'extensions' => array( 'php', 'php7', 'phtml', 'inc', 'twig', 'json' ) ) : array(); 98 // Older releases scanned these fixed directories, even with custom plugin/theme roots. 99 $roots = 1 === $scope ? array( 'wp-content/plugins' => WP_CONTENT_DIR . '/plugins', 'wp-content/themes' => WP_CONTENT_DIR . '/themes' ) : self::scan_roots(); 100 $scan = Siteseau_Scanner::scan( $roots, $limits ); 101 $report['complete'] = $scan['complete']; 102 $report['files'] = count( $scan['hashes'] ); 103 $report['issues'] = $scan['issues']; 104 if ( $scan['complete'] ) { 105 if ( $make_baseline ) { 106 update_option( self::OPT_HASHES, $scan['hashes'], false ); 107 update_option( self::OPT_HASHES_TIME, $report['time'], false ); 108 update_option( 'siteseau_scope', 2, false ); 109 $report['baseline_time'] = $report['time']; 110 } else { 111 $report['changes'] = Siteseau_Scanner::compare( $old, $scan['hashes'] ); 183 112 } 184 113 } 185 114 } 186 if ( ! $printed ) { 187 echo '<tr><td colspan="2">' . esc_html__( 'No changes detected.', 'site-security-auditor' ) . '</td></tr>'; 188 } 189 echo '</tbody></table>'; 115 update_option( self::OPT_REPORT, $report, false ); 116 } finally { 117 delete_option( 'siteseau_scan_lock' ); 190 118 } 191 192 // Hardening checks.193 echo '<hr /><h2>' . esc_html__( 'Hardening Checks', 'site-security-auditor' ) . '</h2>';194 echo '<table class="widefat striped"><thead><tr><th>' . esc_html__( 'Check', 'site-security-auditor' ) . '</th><th>' . esc_html__( 'Status', 'site-security-auditor' ) . '</th></tr></thead><tbody>';195 196 echo '<tr><td>' . esc_html__( 'DISALLOW_FILE_EDIT', 'site-security-auditor' ) . '</td><td>' . ( ! empty( $hardening['disallow_file_edit'] ) ? esc_html__( 'Enabled', 'site-security-auditor' ) : esc_html__( 'Disabled', 'site-security-auditor' ) ) . '</td></tr>';197 echo '<tr><td>' . esc_html__( 'FORCE_SSL_ADMIN', 'site-security-auditor' ) . '</td><td>' . ( ! empty( $hardening['force_ssl_admin'] ) ? esc_html__( 'Enabled', 'site-security-auditor' ) : esc_html__( 'Disabled', 'site-security-auditor' ) ) . '</td></tr>';198 echo '<tr><td>' . esc_html__( 'Plugin auto-updates (general)', 'site-security-auditor' ) . '</td><td>' . ( ! empty( $hardening['plugin_updates_enabled'] ) ? esc_html__( 'Enabled', 'site-security-auditor' ) : esc_html__( 'Disabled', 'site-security-auditor' ) ) . '</td></tr>';199 echo '<tr><td>' . esc_html__( 'XML-RPC availability', 'site-security-auditor' ) . '</td><td>' . ( ! empty( $hardening['xmlrpc_enabled'] ) ? esc_html__( 'Enabled', 'site-security-auditor' ) : esc_html__( 'Disabled', 'site-security-auditor' ) ) . '</td></tr>';200 201 echo '</tbody></table>';202 echo '</div>';203 119 } 204 205 /**206 * Collect plugin info and risk signals.207 *208 * @return array209 */210 120 protected static function collect_plugins_info() { 211 if ( ! function_exists( 'get_plugins' ) ) { 212 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 121 if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } 122 $updates = get_site_transient( 'update_plugins' ); 123 $recent = is_object( $updates ) && ! empty( $updates->last_checked ) && $updates->last_checked > time() - 2 * DAY_IN_SECONDS; 124 $auto = (array) get_site_option( 'auto_update_plugins', array() ); 125 $results = array(); 126 foreach ( get_plugins() as $file => $data ) { 127 $known = $recent && isset( $updates->checked[ $file ] ) && $updates->checked[ $file ] === $data['Version'] && ( isset( $updates->response[ $file ] ) || isset( $updates->no_update[ $file ] ) ); 128 $results[] = array( 129 'name' => $data['Name'], 'version' => $data['Version'], 130 'active' => is_plugin_active( $file ) || is_plugin_active_for_network( $file ), 131 'update' => $known ? ( isset( $updates->response[ $file ] ) ? 'available' : 'none' ) : 'unknown', 132 'auto' => in_array( $file, $auto, true ), 133 ); 213 134 } 214 $all_plugins = get_plugins();215 $active = get_option( 'active_plugins', array() );216 $active_map = array_fill_keys( $active, true );217 218 $updates = get_site_transient( 'update_plugins' );219 $updates_map = array();220 if ( isset( $updates->response ) && is_array( $updates->response ) ) {221 foreach ( $updates->response as $file => $obj ) {222 $updates_map[ $file ] = true;223 }224 }225 226 $results = array();227 foreach ( $all_plugins as $file => $data ) {228 $slug = dirname( $file );229 $info = array(230 'name' => isset( $data['Name'] ) ? $data['Name'] : $file,231 'version' => isset( $data['Version'] ) ? $data['Version'] : '',232 'active' => isset( $active_map[ $file ] ),233 'update_available'=> isset( $updates_map[ $file ] ),234 'last_updated' => __( 'Unknown', 'site-security-auditor' ),235 'stale' => false,236 );237 238 // Try wp.org API for last_updated.239 if ( ! function_exists( 'plugins_api' ) ) {240 241 }242 $args = array(243 'slug' => $slug,244 'fields' => array( 'last_updated' => true ),245 );246 if ( is_admin() && ! function_exists( 'plugins_api' ) ) {247 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';248 }249 $api = @plugins_api( 'plugin_information', $args ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged250 if ( ! is_wp_error( $api ) && ! empty( $api->last_updated ) ) {251 $info['last_updated'] = mysql2date( get_option( 'date_format' ), $api->last_updated );252 $ts = strtotime( $api->last_updated );253 if ( $ts && ( time() - $ts ) > YEAR_IN_SECONDS ) {254 $info['stale'] = true;255 }256 }257 258 $results[] = $info;259 }260 261 135 return $results; 262 136 } 263 264 /** 265 * Directories to scan. 266 * 267 * @return array 268 */ 269 protected static function get_scan_paths() { 137 protected static function hardening_checks() { 270 138 return array( 271 WP_CONTENT_DIR . '/plugins', 272 WP_CONTENT_DIR . '/themes', 139 array( __( 'Encrypted website address', 'site-security-auditor' ), 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ), __( 'Use HTTPS for your public site. Ask your host to configure a certificate and redirects.', 'site-security-auditor' ) ), 140 array( __( 'HTTPS required for administration', 'site-security-auditor' ), (bool) force_ssl_admin(), __( 'Ask your host to check FORCE_SSL_ADMIN and your HTTPS proxy configuration.', 'site-security-auditor' ) ), 141 array( __( 'Dashboard code editing disabled', 'site-security-auditor' ), ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) || ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ), __( 'Consider DISALLOW_FILE_EDIT in wp-config.php. Back up the file and ask your developer before editing it.', 'site-security-auditor' ) ), 142 array( __( 'WordPress debug display off', 'site-security-auditor' ), ! ( defined( 'WP_DEBUG' ) && WP_DEBUG && ( ! defined( 'WP_DEBUG_DISPLAY' ) || WP_DEBUG_DISPLAY ) ), __( 'On production sites, keep WordPress debug messages out of public pages. Server-level error display needs a separate host check.', 'site-security-auditor' ) ), 273 143 ); 274 144 } 275 276 /** 277 * Hash files in given paths. 278 * 279 * @param array $paths Paths. 280 * @return array Map file => hash. 281 */ 282 protected static function hash_paths( $paths ) { 283 $map = array(); 284 285 foreach ( $paths as $path ) { 286 if ( ! is_dir( $path ) ) { 287 continue; 288 } 289 $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path, FilesystemIterator::SKIP_DOTS ) ); 290 foreach ( $iterator as $file ) { 291 /** @var SplFileInfo $file */ 292 $ext = strtolower( pathinfo( $file->getFilename(), PATHINFO_EXTENSION ) ); 293 if ( in_array( $ext, array( 'php', 'php7', 'phtml', 'inc', 'twig', 'json' ), true ) ) { 294 $real = $file->getPathname(); 295 $rel = ltrim( str_replace( WP_CONTENT_DIR, 'wp-content', $real ), '/' ); 296 $hash = @hash_file( 'sha256', $real ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged 297 if ( $hash ) { 298 $map[ $rel ] = $hash; 299 } 300 } 301 } 145 public static function render_page() { 146 if ( ! current_user_can( self::capability() ) ) { return; } 147 $plugins = self::collect_plugins_info(); 148 $checks = self::hardening_checks(); 149 $report = get_option( self::OPT_REPORT, array() ); 150 $baseline_time = (int) get_option( self::OPT_HASHES_TIME, 0 ); 151 $updates = count( array_filter( $plugins, function( $row ) { return 'available' === $row['update']; } ) ); 152 $review = count( array_filter( $checks, function( $row ) { return ! $row[1]; } ) ); 153 require SITESEAU_PLUGIN_DIR . 'includes/dashboard.php'; 154 } 155 public static function action_form( $action, $label, $disabled = false, $confirm = false ) { 156 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="ssa-action">'; 157 wp_nonce_field( $action ); 158 echo '<input type="hidden" name="action" value="' . esc_attr( $action ) . '">'; 159 if ( $confirm ) { 160 echo '<label class="ssa-confirm"><input type="checkbox" required> ' . esc_html__( 'I have reviewed this site and trust its current files. This replaces the previous baseline.', 'site-security-auditor' ) . '</label>'; 302 161 } 303 ksort( $map ); 304 return $map; 305 } 306 307 /** 308 * Compare old and new hash maps. 309 * 310 * @param array $old Old. 311 * @param array $new New. 312 * @return array 313 */ 314 protected static function compare_hashes( $old, $new ) { 315 $old = is_array( $old ) ? $old : array(); 316 $new = is_array( $new ) ? $new : array(); 317 318 $added = array_diff_key( $new, $old ); 319 $removed = array_diff_key( $old, $new ); 320 $modified = array(); 321 322 $common = array_intersect_key( $new, $old ); 323 foreach ( $common as $file => $hash ) { 324 if ( $old[ $file ] !== $hash ) { 325 $modified[ $file ] = $hash; 326 } 327 } 328 329 return array( 330 'added' => array_keys( $added ), 331 'modified' => array_keys( $modified ), 332 'removed' => array_keys( $removed ), 333 ); 334 } 335 336 /** 337 * Hardening checks. 338 * 339 * @return array 340 */ 341 protected static function hardening_checks() { 342 // Generic on/off for auto-updates by type (does not hook into filters). 343 $auto_plugins = function_exists( 'wp_is_auto_update_enabled_for_type' ) ? (bool) wp_is_auto_update_enabled_for_type( 'plugin' ) : false; 344 $xmlrpc_on = apply_filters( 'xmlrpc_enabled', true ); 345 346 return array( 347 'disallow_file_edit' => defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT, 348 'force_ssl_admin' => defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN, 349 'plugin_updates_enabled'=> $auto_plugins, 350 'xmlrpc_enabled' => (bool) $xmlrpc_on, 351 ); 162 echo '<button class="button ' . ( 'ssa_run_scan' === $action ? 'button-primary' : '' ) . '" type="submit"' . disabled( $disabled, true, false ) . '>' . esc_html( $label ) . '</button></form>'; 352 163 } 353 164 } 354 355 165 Siteseau_Site_Security_Auditor::init(); 356 357 endif; -
site-security-auditor/trunk/languages/site-security-auditor.pot
r3365444 r3682123 1 # POT placeholder 1 # Copyright (C) 2026 Gopal Bogati 2 # This file is distributed under the GPL-2.0-or-later. 3 msgid "" 4 msgstr "" 5 "Project-Id-Version: Site Security Auditor 1.4.0\n" 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/trunk\n" 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 8 "Language-Team: LANGUAGE <LL@li.org>\n" 9 "MIME-Version: 1.0\n" 10 "Content-Type: text/plain; charset=UTF-8\n" 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026-09-05T05:49:37+00:00\n" 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.12.0\n" 15 "X-Domain: site-security-auditor\n" 16 17 #. Plugin Name of the plugin 18 #: site-security-auditor.php 19 msgid "Site Security Auditor" 20 msgstr "" 21 22 #. Description of the plugin 23 #: site-security-auditor.php 24 msgid "Review plugin updates, compare plugin and theme files, and follow a clear WordPress security checklist." 25 msgstr "" 26 27 #. Author of the plugin 28 #: site-security-auditor.php 29 msgid "Gopal Bogati" 30 msgstr "" 31 32 #. Author URI of the plugin 33 #: site-security-auditor.php 34 msgid "https://profiles.wordpress.org/gopalbogati/" 35 msgstr "" 36 37 #: includes/dashboard.php:8 38 msgid "SITE SECURITY AUDITOR" 39 msgstr "" 40 41 #: includes/dashboard.php:9 42 msgid "Your security review" 43 msgstr "" 44 45 #: includes/dashboard.php:10 46 msgid "Know what changed. Understand your next step." 47 msgstr "" 48 49 #: includes/dashboard.php:13 50 msgid "Plugin updates available" 51 msgstr "" 52 53 #: includes/dashboard.php:13 54 msgid "Review plugin information" 55 msgstr "" 56 57 #: includes/dashboard.php:14 58 msgid "Settings to review" 59 msgstr "" 60 61 #: includes/dashboard.php:14 62 msgid "Open your checklist" 63 msgstr "" 64 65 #: includes/dashboard.php:15 66 msgid "File comparison" 67 msgstr "" 68 69 #: includes/dashboard.php:15 70 msgid "Baseline ready" 71 msgstr "" 72 73 #: includes/dashboard.php:15 74 msgid "Set up first" 75 msgstr "" 76 77 #: includes/dashboard.php:15 78 msgid "Review files" 79 msgstr "" 80 81 #: includes/dashboard.php:17 82 msgid "These checks reduce guesswork, not all security risks. This is not a malware scanner, firewall, or vulnerability database." 83 msgstr "" 84 85 #: includes/dashboard.php:19 86 msgid "1. Check for file changes" 87 msgstr "" 88 89 #: includes/dashboard.php:20 90 msgid "A baseline is a snapshot of files you trust. Compare against it after maintenance or whenever you want to investigate unexpected changes." 91 msgstr "" 92 93 #: includes/dashboard.php:22 94 msgid "Back up your site and confirm its files are trustworthy." 95 msgstr "" 96 97 #: includes/dashboard.php:23 98 msgid "Save a baseline, then run a comparison when you return." 99 msgstr "" 100 101 #: includes/dashboard.php:24 102 msgid "Review changes with your update history or developer before replacing the baseline." 103 msgstr "" 104 105 #: includes/dashboard.php:27 106 msgid "Baseline saved:" 107 msgstr "" 108 109 #: includes/dashboard.php:29 110 msgid "Compare files now" 111 msgstr "" 112 113 #: includes/dashboard.php:31 114 msgid "Replace the trusted baseline" 115 msgstr "" 116 117 #: includes/dashboard.php:31 118 msgid "Create your first baseline" 119 msgstr "" 120 121 #: includes/dashboard.php:32 122 msgid "Saving a snapshot does not prove that files are clean. Replacing it accepts all current changes as your new reference." 123 msgstr "" 124 125 #: includes/dashboard.php:33 126 msgid "Save trusted baseline" 127 msgstr "" 128 129 #: includes/dashboard.php:36 130 #: includes/dashboard.php:37 131 msgid "Latest file report" 132 msgstr "" 133 134 #. translators: %d: number of files read. 135 #: includes/dashboard.php:39 136 #, php-format 137 msgid "%d files read" 138 msgstr "" 139 140 #: includes/dashboard.php:41 141 msgid "Incomplete — no comparison or baseline replacement was made." 142 msgstr "" 143 144 #: includes/dashboard.php:42 145 msgid "Finish any updates, check file permissions with your host, and retry. Large sites may need a host-level integrity tool." 146 msgstr "" 147 148 #: includes/dashboard.php:45 149 msgid "Baseline saved. Your next comparison will use this snapshot." 150 msgstr "" 151 152 #: includes/dashboard.php:47 153 msgid "Changes are not necessarily malicious: updates and intentional edits also change files. No changes does not guarantee a secure site." 154 msgstr "" 155 156 #: includes/dashboard.php:48 157 msgid "Added" 158 msgstr "" 159 160 #: includes/dashboard.php:48 161 msgid "Modified" 162 msgstr "" 163 164 #: includes/dashboard.php:48 165 msgid "Removed" 166 msgstr "" 167 168 #: includes/dashboard.php:53 169 msgid "Showing the first 100 paths. Download the full report below." 170 msgstr "" 171 172 #: includes/dashboard.php:57 173 msgid "Download file report (JSON)" 174 msgstr "" 175 176 #: includes/dashboard.php:58 177 msgid "The download includes relative file paths. Share only with people you trust." 178 msgstr "" 179 180 #: includes/dashboard.php:61 181 msgid "What is included in a scan?" 182 msgstr "" 183 184 #: includes/dashboard.php:62 185 msgid "Plugin and registered theme directories: PHP, PHP7, PHTML, INC, TWIG, JSON, JS and CSS files. WordPress core, uploads, must-use plugins, database content and other file types are excluded. Symbolic links are not followed and make a scan incomplete." 186 msgstr "" 187 188 #: includes/dashboard.php:63 189 msgid "Limits: 20,000 directory entries, 100 MiB total, 8 MiB per file and an approximately 8-second processing budget. An incomplete scan never replaces your baseline. Files should not be edited while a scan runs." 190 msgstr "" 191 192 #: includes/dashboard.php:64 193 msgid "Your older baseline excludes JS and CSS. Comparisons keep that scope until you save a new trusted baseline." 194 msgstr "" 195 196 #: includes/dashboard.php:68 197 msgid "2. Review your plugins" 198 msgstr "" 199 200 #: includes/dashboard.php:69 201 msgid "Uses the update information already stored by WordPress. Unknown means information is missing or more than two days old; premium plugins may use their own update systems." 202 msgstr "" 203 204 #: includes/dashboard.php:70 205 msgid "Open WordPress Updates" 206 msgstr "" 207 208 #: includes/dashboard.php:71 209 msgid "Manage plugins" 210 msgstr "" 211 212 #: includes/dashboard.php:72 213 msgid "Plugin maintenance table" 214 msgstr "" 215 216 #: includes/dashboard.php:74 217 msgid "Plugin" 218 msgstr "" 219 220 #: includes/dashboard.php:74 221 msgid "Status" 222 msgstr "" 223 224 #: includes/dashboard.php:74 225 msgid "Update information" 226 msgstr "" 227 228 #: includes/dashboard.php:74 229 msgid "Next step" 230 msgstr "" 231 232 #: includes/dashboard.php:78 233 msgid "Active" 234 msgstr "" 235 236 #: includes/dashboard.php:78 237 msgid "Inactive" 238 msgstr "" 239 240 #: includes/dashboard.php:79 241 msgid "Update available" 242 msgstr "" 243 244 #: includes/dashboard.php:79 245 msgid "Unknown — refresh in Updates" 246 msgstr "" 247 248 #: includes/dashboard.php:79 249 msgid "No update reported" 250 msgstr "" 251 252 #: includes/dashboard.php:80 253 msgid "Back up, review the release notes, then update." 254 msgstr "" 255 256 #: includes/dashboard.php:80 257 msgid "Remove it if no longer needed, after checking dependencies." 258 msgstr "" 259 260 #: includes/dashboard.php:80 261 msgid "Keep maintained and review auto-update settings." 262 msgstr "" 263 264 #: includes/dashboard.php:81 265 msgid "Auto-updates selected in WordPress; host or plugin policy may override this." 266 msgstr "" 267 268 #: includes/dashboard.php:84 269 msgid "No update reported is not a security verdict. This tool does not check a vulnerability database or certify plugins as safe." 270 msgstr "" 271 272 #: includes/dashboard.php:87 273 msgid "3. Follow your security checklist" 274 msgstr "" 275 276 #: includes/dashboard.php:89 277 msgid "Configured" 278 msgstr "" 279 280 #: includes/dashboard.php:89 281 msgid "Review" 282 msgstr "" 283 284 #: includes/dashboard.php:92 285 msgid "Also check these with your host or administrator" 286 msgstr "" 287 288 #: includes/dashboard.php:93 289 msgid "Test your backups, use unique passwords and two-factor authentication, remove unused accounts, and keep WordPress and themes updated. These are manual checks; this plugin does not verify them." 290 msgstr "" 291 292 #: includes/dashboard.php:94 293 msgid "Open WordPress Site Health" 294 msgstr "" 295 296 #: includes/dashboard.php:95 297 msgid "Read the official security guide" 298 msgstr "" 299 300 #: includes/dashboard.php:97 301 msgid "Read-only checks. No automatic file edits, tracking, or additional remote metadata requests. Only the latest file report is stored." 302 msgstr "" 303 304 #: site-security-auditor.php:36 305 msgid "Security Audit" 306 msgstr "" 307 308 #: site-security-auditor.php:45 309 msgid "You are not allowed to perform this action." 310 msgstr "" 311 312 #: site-security-auditor.php:66 313 msgid "Run a file check first." 314 msgstr "" 315 316 #: site-security-auditor.php:95 317 msgid "Create a trusted baseline before comparing files." 318 msgstr "" 319 320 #: site-security-auditor.php:139 321 msgid "Encrypted website address" 322 msgstr "" 323 324 #: site-security-auditor.php:139 325 msgid "Use HTTPS for your public site. Ask your host to configure a certificate and redirects." 326 msgstr "" 327 328 #: site-security-auditor.php:140 329 msgid "HTTPS required for administration" 330 msgstr "" 331 332 #: site-security-auditor.php:140 333 msgid "Ask your host to check FORCE_SSL_ADMIN and your HTTPS proxy configuration." 334 msgstr "" 335 336 #: site-security-auditor.php:141 337 msgid "Dashboard code editing disabled" 338 msgstr "" 339 340 #: site-security-auditor.php:141 341 msgid "Consider DISALLOW_FILE_EDIT in wp-config.php. Back up the file and ask your developer before editing it." 342 msgstr "" 343 344 #: site-security-auditor.php:142 345 msgid "WordPress debug display off" 346 msgstr "" 347 348 #: site-security-auditor.php:142 349 msgid "On production sites, keep WordPress debug messages out of public pages. Server-level error display needs a separate host check." 350 msgstr "" 351 352 #: site-security-auditor.php:160 353 msgid "I have reviewed this site and trust its current files. This replaces the previous baseline." 354 msgstr "" -
site-security-auditor/trunk/readme.txt
r3365450 r3682123 1 === Site Security Auditor ===1 === Site Security Auditor – Plugin & File Checks === 2 2 Contributors: gopalbogati 3 Tags: security audit, file integrity, plugin updates, security checklist, site health 4 Requires at least: 5.8 5 Tested up to: 7.0 3 6 Requires PHP: 7.4 4 Tested up to: 6.8 5 Stable tag: 1.0 7 Stable tag: 1.4.0 6 8 License: GPLv2 or later 7 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 8 10 9 A simple admin tool to audit plugins, file integrity (baseline + changes), and hardening settings. No tracking or updater hooks.11 Review plugin updates, detect plugin and theme file changes, and follow a clear WordPress security checklist. No account required. 10 12 11 13 == Description == 12 Site Security Auditor helps admins spot risks from outdated/inactive plugins, monitor file changes in plugins/themes, and review hardening settings like DISALLOW_FILE_EDIT or FORCE_SSL_ADMIN. It uses only WordPress core APIs and stores data in options. 14 15 Site Security Auditor gives WordPress administrators a practical security review dashboard. See plugin update information, compare plugin and theme files against a trusted snapshot, and understand which settings deserve attention. 16 17 Open **Tools → Security Audit** to start. Each section explains what it checks, what the result means, and what to do next. It is designed for routine maintenance and investigating unexpected file changes, without automatically editing your site. 18 19 = What you can do = 20 21 * **Review plugin updates:** see available updates, inactive plugins and missing or stale update information. Open the familiar WordPress update screen to take action. 22 * **Compare files:** save a trusted SHA-256 baseline, then identify added, modified and removed code files in plugin and theme directories. 23 * **Return to your latest report:** the most recent file check stays visible with its timestamp and file count. Simply opening the dashboard does not start another scan. 24 * **Download a file report:** export the latest result as JSON to review with your developer or host. 25 * **Follow a plain-language checklist:** review HTTPS configuration, dashboard code editing and WordPress debug display, with links to Site Health and official guidance. 26 * **Use a responsive dashboard:** clear sections, keyboard-accessible controls and a scrollable plugin table on small screens. 27 28 = A useful maintenance routine = 29 30 1. Review available updates and take a backup. 31 2. Confirm your files are trustworthy before saving the first baseline. 32 3. Run a comparison after maintenance or when investigating changes. 33 4. Match changed files to intentional updates or edits. 34 5. Replace the baseline only after reviewing and trusting the current files. 35 36 A baseline records the current state; it does not prove that state is clean. Keep independent backups and ask a trusted professional about unexplained changes. 37 38 = Scope and limitations = 39 40 New baselines cover PHP, PHP7, PHTML, INC, TWIG, JSON, JS and CSS files inside the configured plugin directory and registered theme directories. WordPress core, uploads, must-use plugins, database content and other file types are excluded. Symbolic links are not followed and cause an incomplete result. 41 42 Each run is bounded to 20,000 directory entries, 100 MiB of file data, 8 MiB per file and an approximately 8-second processing budget. Unreadable files or exceeded limits produce an incomplete report. Incomplete scans never replace a baseline or present a partial comparison as complete. Large sites may need a host-level integrity tool. Avoid editing files or running updates during a scan. 43 44 This plugin is **not a malware scanner, firewall or vulnerability database**. It does not repair files, guarantee security, or certify a plugin as safe. An update or legitimate edit can change a file; unchanged files can still contain problems. 45 46 = Privacy and performance = 47 48 No account, API key, telemetry or additional remote metadata requests are required. The dashboard uses the update information already maintained by WordPress; WordPress itself may contact its normal update services. 49 50 File hashes, relative paths, baseline time and the latest file report are stored in the site's options, without autoloading them. Reports are restricted to administrators; on multisite they require a network administrator. Downloaded reports contain relative file paths, so share them only with trusted people. Deactivation and deletion preserve these records to avoid silently discarding your trusted reference. 13 51 14 52 == Installation == 15 1. Upload the ZIP via Plugins → Add New → Upload Plugin16 2. Activate17 3. Go to Tools → Security Audit18 4. Create/Update Baseline → Scan Now19 53 20 == Changeling == 54 1. Install Site Security Auditor from Plugins → Add New, or upload the plugin ZIP. 55 2. Activate the plugin. 56 3. Open Tools → Security Audit using an administrator account. 57 4. Read the baseline warning, confirm that you trust the current files, and choose Save trusted baseline. 58 5. Return and select Compare files now to review changes. 59 60 On multisite, a network administrator can open the dashboard from a site's Tools menu. Baselines and reports are stored per site, while plugin and theme files may be shared across the network. 61 62 == Frequently Asked Questions == 63 64 = Does it scan for malware or known vulnerabilities? = 65 66 No. It compares code-file hashes with your own baseline and shows maintenance/configuration checks. Use other appropriate controls and professional advice alongside this tool. 67 68 = When should I replace the baseline? = 69 70 Only after verifying the current files, for example after reviewing an intentional plugin update. Replacing it accepts the current state as the reference and removes the previous comparison point. 71 72 = Why does a plugin say Unknown? = 73 74 WordPress has not supplied current update information for it, or that information is more than two days old. Open WordPress Updates to refresh it. Some premium or custom plugins use separate update systems. Unknown does not mean unsafe or safe. 75 76 = What happens to my existing baseline when I update? = 77 78 Your baseline is retained. Baselines made before 1.4.0 continue comparing their original file types, without JS and CSS. A newly saved trusted baseline includes JS and CSS. Review changes before replacing it. 79 80 = Why is a scan incomplete? = 81 82 The scan reached a resource limit, encountered a symbolic link, could not read a directory/file, or found a file changing during the run. The report explains the issue. Finish updates and ask your host to check permissions; large sites may need another integrity tool. 83 84 = Does it change my security settings automatically? = 85 86 No. It provides guidance and links. Configuration changes can affect hosting or integrations, so review them with your administrator. 87 88 = Can I use this every day? = 89 90 You can manually review the dashboard and compare files whenever useful. There are no scheduled scans or email alerts in this version. The dashboard stores only the latest file report; download reports you need to keep. 91 92 == Screenshots == 93 94 1. Security review dashboard with maintenance summaries and the trusted-baseline workflow. 95 2. Saved file comparison with added, modified and removed file groups. 96 3. Plugin maintenance information and a plain-language configuration checklist. 97 98 == Changelog == 99 100 = 1.4.0 = 101 * New responsive dashboard, guided baseline workflow and actionable security checklist. 102 * Save the latest file report and download it as JSON. 103 * Run scans only through authenticated, nonce-protected POST actions. 104 * Preserve baselines when scans are incomplete; add resource limits and explicit coverage warnings. 105 * Include JS and CSS in new baselines while preserving legacy comparison scope. 106 * Use cached WordPress update data instead of per-plugin remote lookups on dashboard views. 107 * Recognize network-active plugins and restrict multisite access to network administrators. 108 * Refresh directory branding, documentation, screenshots and release metadata. 109 21 110 = 1.0 = 22 * Add full dashboard sections and escaping 23 * Fix text domain packaging 111 * Initial dashboard for plugin information, file baselines and hardening checks. 112 113 == Upgrade Notice == 114 115 = 1.4.0 = 116 New dashboard and safer file checks. Existing baselines remain intact; newly saved baselines also include JS and CSS. Review changes before replacing a baseline. 117 -
site-security-auditor/trunk/site-security-auditor.php
r3365444 r3682123 1 1 <?php 2 2 /** 3 * Plugin Name: Site Security Auditor4 * Description: Audits plugins, file integrity, and hardening to improve site security.5 * Version: 1.3.36 * Author: Gopal Bogati7 * Author URI: https://wordpress.org/8 * Text Domain: site-security-auditor9 * Domain Path: /languages3 * Plugin Name: Site Security Auditor 4 * Description: Review plugin updates, compare plugin and theme files, and follow a clear WordPress security checklist. 5 * Version: 1.4.0 6 * Author: Gopal Bogati 7 * Author URI: https://profiles.wordpress.org/gopalbogati/ 8 * Text Domain: site-security-auditor 9 * Domain Path: /languages 10 10 * Requires at least: 5.8 11 * Tested up to: 6.8 12 * Requires PHP: 7.4 13 * License: GPL-2.0+ 11 * Requires PHP: 7.4 12 * License: GPL-2.0-or-later 14 13 */ 14 if ( ! defined( 'ABSPATH' ) ) { exit; } 15 15 define( 'SITESEAU_PLUGIN_FILE', __FILE__ ); 16 16 define( 'SITESEAU_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 17 17 define( 'SITESEAU_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 18 require_once SITESEAU_PLUGIN_DIR . 'includes/class-scanner.php'; 18 19 20 class Siteseau_Site_Security_Auditor { 21 const OPT_HASHES = 'siteseau_hashes'; 22 const OPT_HASHES_TIME = 'siteseau_hashes_time'; 23 const OPT_REPORT = 'siteseau_report'; 19 24 20 if ( ! defined( 'ABSPATH' ) ) {21 exit;22 }23 24 if ( ! class_exists( 'Siteseau_Site_Security_Auditor' ) ) :25 26 /**27 * Main plugin class.28 */29 class Siteseau_Site_Security_Auditor {30 31 const OPT_HASHES = 'siteseau_hashes';32 const OPT_HASHES_TIME = 'siteseau_hashes_time';33 34 /**35 * Bootstrap.36 */37 25 public static function init() { 38 26 add_action( 'admin_menu', array( __CLASS__, 'add_menu' ) ); 27 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'assets' ) ); 39 28 add_action( 'admin_post_ssa_make_baseline', array( __CLASS__, 'handle_baseline' ) ); 40 29 add_action( 'admin_post_ssa_run_scan', array( __CLASS__, 'handle_scan' ) ); 30 add_action( 'admin_post_ssa_export', array( __CLASS__, 'handle_export' ) ); 41 31 } 42 43 /** 44 * Add Tools -> Security Audit page. 45 */ 32 public static function capability() { 33 return is_multisite() ? 'manage_network_options' : 'manage_options'; 34 } 46 35 public static function add_menu() { 47 add_management_page( 48 __( 'Security Audit', 'site-security-auditor' ), 49 __( 'Security Audit', 'site-security-auditor' ), 50 'manage_options', 51 'site-security-auditor', 52 array( __CLASS__, 'render_page' ) 53 ); 36 add_management_page( __( 'Security Audit', 'site-security-auditor' ), __( 'Security Audit', 'site-security-auditor' ), self::capability(), 'site-security-auditor', array( __CLASS__, 'render_page' ) ); 54 37 } 55 56 /** 57 * Create/update baseline handler. 58 */ 38 public static function assets( $hook ) { 39 if ( 'tools_page_site-security-auditor' === $hook ) { 40 wp_enqueue_style( 'siteseau-admin', SITESEAU_PLUGIN_URL . 'assets/admin.css', array(), '1.4.0' ); 41 } 42 } 43 private static function authorize( $action ) { 44 if ( ! current_user_can( self::capability() ) || 'POST' !== ( isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : '' ) ) { 45 wp_die( esc_html__( 'You are not allowed to perform this action.', 'site-security-auditor' ), '', array( 'response' => 403 ) ); 46 } 47 check_admin_referer( $action ); 48 } 59 49 public static function handle_baseline() { 60 if ( ! current_user_can( 'manage_options' ) ) {61 wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'site-security-auditor' ));62 }63 check_admin_referer( 'ssa_make_baseline' );64 65 $paths = self::get_scan_paths();66 $hashes = self::hash_paths( $paths);67 update_option( self::OPT_HASHES, $hashes, false);68 update_option( self::OPT_HASHES_TIME, time(), false );69 70 wp_safe_redirect( ad d_query_arg( array( 'page' => 'site-security-auditor', 'ssa_msg' => 'baseline_done' ), admin_url( 'tools.php' )) );50 self::authorize( 'ssa_make_baseline' ); 51 self::run_audit( true ); 52 self::redirect(); 53 } 54 public static function handle_scan() { 55 self::authorize( 'ssa_run_scan' ); 56 self::run_audit( false ); 57 self::redirect(); 58 } 59 private static function redirect() { 60 wp_safe_redirect( admin_url( 'tools.php?page=site-security-auditor' ) ); 71 61 exit; 72 62 } 73 74 /** 75 * Run scan handler. 76 */ 77 public static function handle_scan() { 78 if ( ! current_user_can( 'manage_options' ) ) { 79 wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'site-security-auditor' ) ); 80 } 81 check_admin_referer( 'ssa_run_scan' ); 82 83 wp_safe_redirect( add_query_arg( array( 'page' => 'site-security-auditor', 'ssa_scan' => '1' ), admin_url( 'tools.php' ) ) ); 63 public static function handle_export() { 64 self::authorize( 'ssa_export' ); 65 $report = get_option( self::OPT_REPORT, array() ); 66 if ( ! $report ) { wp_die( esc_html__( 'Run a file check first.', 'site-security-auditor' ) ); } 67 nocache_headers(); 68 header( 'Content-Type: application/json; charset=utf-8' ); 69 header( 'Content-Disposition: attachment; filename="site-security-auditor-report.json"' ); 70 echo wp_json_encode( $report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); 84 71 exit; 85 72 } 86 87 /** 88 * Render admin page. 89 */ 90 public static function render_page() { 91 if ( ! current_user_can( 'manage_options' ) ) { 92 return; 73 protected static function scan_roots() { 74 global $wp_theme_directories; 75 $roots = array( 'wp-content/plugins' => WP_PLUGIN_DIR ); 76 $themes = (array) $wp_theme_directories; 77 if ( ! $themes ) { $themes = array( WP_CONTENT_DIR . '/themes' ); } 78 foreach ( array_values( array_unique( $themes ) ) as $i => $path ) { 79 $label = wp_normalize_path( $path ) === wp_normalize_path( WP_CONTENT_DIR . '/themes' ) ? 'wp-content/themes' : 'theme-root-' . $i; 80 $roots[ $label ] = $path; 93 81 } 94 95 $plugins_info = self::collect_plugins_info(); 96 $hardening = self::hardening_checks(); 97 $baseline_ts = intval( get_option( self::OPT_HASHES_TIME, 0 ) ); 98 $hashes = get_option( self::OPT_HASHES, array() ); 99 $scan_results = array(); 100 101 if ( isset( $_GET['ssa_scan'] ) && '1' === $_GET['ssa_scan'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended 102 $scan_results = self::compare_hashes( is_array( $hashes ) ? $hashes : array(), self::hash_paths( self::get_scan_paths() ) ); 103 } 104 105 echo '<div class="wrap">'; 106 echo '<h1>' . esc_html__( 'Security Audit Dashboard', 'site-security-auditor' ) . '</h1>'; 107 108 if ( isset( $_GET['ssa_msg'] ) && 'baseline_done' === $_GET['ssa_msg'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended 109 echo '<div class="notice notice-success"><p>' . esc_html__( 'File integrity baseline created/updated.', 'site-security-auditor' ) . '</p></div>'; 110 } 111 112 // Plugins Risk Overview. 113 echo '<h2>' . esc_html__( 'Plugins Risk Overview', 'site-security-auditor' ) . '</h2>'; 114 echo '<p>' . esc_html__( 'Flags plugins with updates available, last updated over a year ago, or inactive status.', 'site-security-auditor' ) . '</p>'; 115 echo '<table class="widefat striped"><thead><tr>'; 116 echo '<th>' . esc_html__( 'Plugin', 'site-security-auditor' ) . '</th>'; 117 echo '<th>' . esc_html__( 'Version', 'site-security-auditor' ) . '</th>'; 118 echo '<th>' . esc_html__( 'Status', 'site-security-auditor' ) . '</th>'; 119 echo '<th>' . esc_html__( 'Update Available', 'site-security-auditor' ) . '</th>'; 120 echo '<th>' . esc_html__( 'Last Updated', 'site-security-auditor' ) . '</th>'; 121 echo '<th>' . esc_html__( 'Risk', 'site-security-auditor' ) . '</th>'; 122 echo '</tr></thead><tbody>'; 123 124 if ( empty( $plugins_info ) ) { 125 echo '<tr><td colspan="6">' . esc_html__( 'No plugins found.', 'site-security-auditor' ) . '</td></tr>'; 126 } else { 127 foreach ( $plugins_info as $row ) { 128 $risk_labels = array(); 129 if ( ! empty( $row['update_available'] ) ) { 130 $risk_labels[] = __( 'Update available', 'site-security-auditor' ); 131 } 132 if ( ! empty( $row['stale'] ) ) { 133 $risk_labels[] = __( 'Stale (> 1 year)', 'site-security-auditor' ); 134 } 135 if ( empty( $row['active'] ) ) { 136 $risk_labels[] = __( 'Inactive', 'site-security-auditor' ); 137 } 138 echo '<tr>'; 139 echo '<td>' . esc_html( $row['name'] ) . '</td>'; 140 echo '<td>' . esc_html( $row['version'] ) . '</td>'; 141 echo '<td>' . ( ! empty( $row['active'] ) ? esc_html__( 'Active', 'site-security-auditor' ) : esc_html__( 'Inactive', 'site-security-auditor' ) ) . '</td>'; 142 echo '<td>' . ( ! empty( $row['update_available'] ) ? esc_html__( 'Yes', 'site-security-auditor' ) : esc_html__( 'No', 'site-security-auditor' ) ) . '</td>'; 143 echo '<td>' . esc_html( $row['last_updated'] ) . '</td>'; 144 echo '<td>' . ( $risk_labels ? esc_html( implode( ', ', $risk_labels ) ) : esc_html__( 'OK', 'site-security-auditor' ) ) . '</td>'; 145 echo '</tr>'; 146 } 147 } 148 echo '</tbody></table>'; 149 150 // File Integrity Monitor. 151 echo '<hr /><h2>' . esc_html__( 'File Integrity Monitor', 'site-security-auditor' ) . '</h2>'; 152 echo '<p>' . esc_html__( 'Create a hash baseline for plugins/themes and scan to detect added, modified or removed files.', 'site-security-auditor' ) . '</p>'; 153 154 if ( $baseline_ts ) { 155 /* translators: %s: date/time. */ 156 $baseline_text = sprintf( esc_html__( 'Baseline last updated: %s', 'site-security-auditor' ), wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $baseline_ts ) ); 157 echo '<p>' . esc_html( $baseline_text ) . '</p>'; 158 } else { 159 echo '<p>' . esc_html__( 'No baseline yet.', 'site-security-auditor' ) . '</p>'; 160 } 161 162 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" style="display:inline-block;margin-right:10px;">'; 163 wp_nonce_field( 'ssa_make_baseline' ); 164 echo '<input type="hidden" name="action" value="ssa_make_baseline" />'; 165 echo '<button type="submit" class="button button-primary" onclick="return confirm(\'' . esc_js( __( 'Create/Update baseline now?', 'site-security-auditor' ) ) . '\');">' . esc_html__( 'Create/Update Baseline', 'site-security-auditor' ) . '</button>'; 166 echo '</form>'; 167 168 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" style="display:inline-block;">'; 169 wp_nonce_field( 'ssa_run_scan' ); 170 echo '<input type="hidden" name="action" value="ssa_run_scan" />'; 171 echo '<button type="submit" class="button">' . esc_html__( 'Scan Now', 'site-security-auditor' ) . '</button>'; 172 echo '</form>'; 173 174 if ( ! empty( $scan_results ) ) { 175 echo '<h3 style="margin-top:20px;">' . esc_html__( 'Scan Results', 'site-security-auditor' ) . '</h3>'; 176 echo '<table class="widefat striped"><thead><tr><th>' . esc_html__( 'Change', 'site-security-auditor' ) . '</th><th>' . esc_html__( 'File', 'site-security-auditor' ) . '</th></tr></thead><tbody>'; 177 $printed = false; 178 foreach ( array( 'added', 'modified', 'removed' ) as $key ) { 179 if ( ! empty( $scan_results[ $key ] ) ) { 180 foreach ( $scan_results[ $key ] as $file ) { 181 echo '<tr><td>' . esc_html( ucfirst( $key ) ) . '</td><td>' . esc_html( $file ) . '</td></tr>'; 182 $printed = true; 82 return $roots; 83 } 84 /** Protected operation, invoked only by the authorized POST handlers. */ 85 protected static function run_audit( $make_baseline ) { 86 $lock = (int) get_option( 'siteseau_scan_lock', 0 ); 87 if ( $lock && $lock < time() - 300 ) { delete_option( 'siteseau_scan_lock' ); } 88 if ( ! add_option( 'siteseau_scan_lock', time(), '', false ) ) { return; } 89 try { 90 $baseline_time = (int) get_option( self::OPT_HASHES_TIME, 0 ); 91 $old = get_option( self::OPT_HASHES, array() ); 92 $scope = $make_baseline ? 2 : (int) get_option( 'siteseau_scope', 1 ); 93 $report = array( 'time' => time(), 'baseline_time' => $baseline_time, 'scope' => $scope, 'kind' => $make_baseline ? 'baseline' : 'comparison', 'complete' => false, 'files' => 0, 'issues' => array(), 'changes' => array() ); 94 if ( ! $make_baseline && ( ! $baseline_time || ! is_array( $old ) ) ) { 95 $report['issues'][] = __( 'Create a trusted baseline before comparing files.', 'site-security-auditor' ); 96 } else { 97 $limits = 1 === $scope ? array( 'extensions' => array( 'php', 'php7', 'phtml', 'inc', 'twig', 'json' ) ) : array(); 98 // Older releases scanned these fixed directories, even with custom plugin/theme roots. 99 $roots = 1 === $scope ? array( 'wp-content/plugins' => WP_CONTENT_DIR . '/plugins', 'wp-content/themes' => WP_CONTENT_DIR . '/themes' ) : self::scan_roots(); 100 $scan = Siteseau_Scanner::scan( $roots, $limits ); 101 $report['complete'] = $scan['complete']; 102 $report['files'] = count( $scan['hashes'] ); 103 $report['issues'] = $scan['issues']; 104 if ( $scan['complete'] ) { 105 if ( $make_baseline ) { 106 update_option( self::OPT_HASHES, $scan['hashes'], false ); 107 update_option( self::OPT_HASHES_TIME, $report['time'], false ); 108 update_option( 'siteseau_scope', 2, false ); 109 $report['baseline_time'] = $report['time']; 110 } else { 111 $report['changes'] = Siteseau_Scanner::compare( $old, $scan['hashes'] ); 183 112 } 184 113 } 185 114 } 186 if ( ! $printed ) { 187 echo '<tr><td colspan="2">' . esc_html__( 'No changes detected.', 'site-security-auditor' ) . '</td></tr>'; 188 } 189 echo '</tbody></table>'; 115 update_option( self::OPT_REPORT, $report, false ); 116 } finally { 117 delete_option( 'siteseau_scan_lock' ); 190 118 } 191 192 // Hardening checks.193 echo '<hr /><h2>' . esc_html__( 'Hardening Checks', 'site-security-auditor' ) . '</h2>';194 echo '<table class="widefat striped"><thead><tr><th>' . esc_html__( 'Check', 'site-security-auditor' ) . '</th><th>' . esc_html__( 'Status', 'site-security-auditor' ) . '</th></tr></thead><tbody>';195 196 echo '<tr><td>' . esc_html__( 'DISALLOW_FILE_EDIT', 'site-security-auditor' ) . '</td><td>' . ( ! empty( $hardening['disallow_file_edit'] ) ? esc_html__( 'Enabled', 'site-security-auditor' ) : esc_html__( 'Disabled', 'site-security-auditor' ) ) . '</td></tr>';197 echo '<tr><td>' . esc_html__( 'FORCE_SSL_ADMIN', 'site-security-auditor' ) . '</td><td>' . ( ! empty( $hardening['force_ssl_admin'] ) ? esc_html__( 'Enabled', 'site-security-auditor' ) : esc_html__( 'Disabled', 'site-security-auditor' ) ) . '</td></tr>';198 echo '<tr><td>' . esc_html__( 'Plugin auto-updates (general)', 'site-security-auditor' ) . '</td><td>' . ( ! empty( $hardening['plugin_updates_enabled'] ) ? esc_html__( 'Enabled', 'site-security-auditor' ) : esc_html__( 'Disabled', 'site-security-auditor' ) ) . '</td></tr>';199 echo '<tr><td>' . esc_html__( 'XML-RPC availability', 'site-security-auditor' ) . '</td><td>' . ( ! empty( $hardening['xmlrpc_enabled'] ) ? esc_html__( 'Enabled', 'site-security-auditor' ) : esc_html__( 'Disabled', 'site-security-auditor' ) ) . '</td></tr>';200 201 echo '</tbody></table>';202 echo '</div>';203 119 } 204 205 /**206 * Collect plugin info and risk signals.207 *208 * @return array209 */210 120 protected static function collect_plugins_info() { 211 if ( ! function_exists( 'get_plugins' ) ) { 212 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 121 if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } 122 $updates = get_site_transient( 'update_plugins' ); 123 $recent = is_object( $updates ) && ! empty( $updates->last_checked ) && $updates->last_checked > time() - 2 * DAY_IN_SECONDS; 124 $auto = (array) get_site_option( 'auto_update_plugins', array() ); 125 $results = array(); 126 foreach ( get_plugins() as $file => $data ) { 127 $known = $recent && isset( $updates->checked[ $file ] ) && $updates->checked[ $file ] === $data['Version'] && ( isset( $updates->response[ $file ] ) || isset( $updates->no_update[ $file ] ) ); 128 $results[] = array( 129 'name' => $data['Name'], 'version' => $data['Version'], 130 'active' => is_plugin_active( $file ) || is_plugin_active_for_network( $file ), 131 'update' => $known ? ( isset( $updates->response[ $file ] ) ? 'available' : 'none' ) : 'unknown', 132 'auto' => in_array( $file, $auto, true ), 133 ); 213 134 } 214 $all_plugins = get_plugins();215 $active = get_option( 'active_plugins', array() );216 $active_map = array_fill_keys( $active, true );217 218 $updates = get_site_transient( 'update_plugins' );219 $updates_map = array();220 if ( isset( $updates->response ) && is_array( $updates->response ) ) {221 foreach ( $updates->response as $file => $obj ) {222 $updates_map[ $file ] = true;223 }224 }225 226 $results = array();227 foreach ( $all_plugins as $file => $data ) {228 $slug = dirname( $file );229 $info = array(230 'name' => isset( $data['Name'] ) ? $data['Name'] : $file,231 'version' => isset( $data['Version'] ) ? $data['Version'] : '',232 'active' => isset( $active_map[ $file ] ),233 'update_available'=> isset( $updates_map[ $file ] ),234 'last_updated' => __( 'Unknown', 'site-security-auditor' ),235 'stale' => false,236 );237 238 // Try wp.org API for last_updated.239 if ( ! function_exists( 'plugins_api' ) ) {240 241 }242 $args = array(243 'slug' => $slug,244 'fields' => array( 'last_updated' => true ),245 );246 if ( is_admin() && ! function_exists( 'plugins_api' ) ) {247 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';248 }249 $api = @plugins_api( 'plugin_information', $args ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged250 if ( ! is_wp_error( $api ) && ! empty( $api->last_updated ) ) {251 $info['last_updated'] = mysql2date( get_option( 'date_format' ), $api->last_updated );252 $ts = strtotime( $api->last_updated );253 if ( $ts && ( time() - $ts ) > YEAR_IN_SECONDS ) {254 $info['stale'] = true;255 }256 }257 258 $results[] = $info;259 }260 261 135 return $results; 262 136 } 263 264 /** 265 * Directories to scan. 266 * 267 * @return array 268 */ 269 protected static function get_scan_paths() { 137 protected static function hardening_checks() { 270 138 return array( 271 WP_CONTENT_DIR . '/plugins', 272 WP_CONTENT_DIR . '/themes', 139 array( __( 'Encrypted website address', 'site-security-auditor' ), 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ), __( 'Use HTTPS for your public site. Ask your host to configure a certificate and redirects.', 'site-security-auditor' ) ), 140 array( __( 'HTTPS required for administration', 'site-security-auditor' ), (bool) force_ssl_admin(), __( 'Ask your host to check FORCE_SSL_ADMIN and your HTTPS proxy configuration.', 'site-security-auditor' ) ), 141 array( __( 'Dashboard code editing disabled', 'site-security-auditor' ), ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) || ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ), __( 'Consider DISALLOW_FILE_EDIT in wp-config.php. Back up the file and ask your developer before editing it.', 'site-security-auditor' ) ), 142 array( __( 'WordPress debug display off', 'site-security-auditor' ), ! ( defined( 'WP_DEBUG' ) && WP_DEBUG && ( ! defined( 'WP_DEBUG_DISPLAY' ) || WP_DEBUG_DISPLAY ) ), __( 'On production sites, keep WordPress debug messages out of public pages. Server-level error display needs a separate host check.', 'site-security-auditor' ) ), 273 143 ); 274 144 } 275 276 /** 277 * Hash files in given paths. 278 * 279 * @param array $paths Paths. 280 * @return array Map file => hash. 281 */ 282 protected static function hash_paths( $paths ) { 283 $map = array(); 284 285 foreach ( $paths as $path ) { 286 if ( ! is_dir( $path ) ) { 287 continue; 288 } 289 $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path, FilesystemIterator::SKIP_DOTS ) ); 290 foreach ( $iterator as $file ) { 291 /** @var SplFileInfo $file */ 292 $ext = strtolower( pathinfo( $file->getFilename(), PATHINFO_EXTENSION ) ); 293 if ( in_array( $ext, array( 'php', 'php7', 'phtml', 'inc', 'twig', 'json' ), true ) ) { 294 $real = $file->getPathname(); 295 $rel = ltrim( str_replace( WP_CONTENT_DIR, 'wp-content', $real ), '/' ); 296 $hash = @hash_file( 'sha256', $real ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged 297 if ( $hash ) { 298 $map[ $rel ] = $hash; 299 } 300 } 301 } 145 public static function render_page() { 146 if ( ! current_user_can( self::capability() ) ) { return; } 147 $plugins = self::collect_plugins_info(); 148 $checks = self::hardening_checks(); 149 $report = get_option( self::OPT_REPORT, array() ); 150 $baseline_time = (int) get_option( self::OPT_HASHES_TIME, 0 ); 151 $updates = count( array_filter( $plugins, function( $row ) { return 'available' === $row['update']; } ) ); 152 $review = count( array_filter( $checks, function( $row ) { return ! $row[1]; } ) ); 153 require SITESEAU_PLUGIN_DIR . 'includes/dashboard.php'; 154 } 155 public static function action_form( $action, $label, $disabled = false, $confirm = false ) { 156 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="ssa-action">'; 157 wp_nonce_field( $action ); 158 echo '<input type="hidden" name="action" value="' . esc_attr( $action ) . '">'; 159 if ( $confirm ) { 160 echo '<label class="ssa-confirm"><input type="checkbox" required> ' . esc_html__( 'I have reviewed this site and trust its current files. This replaces the previous baseline.', 'site-security-auditor' ) . '</label>'; 302 161 } 303 ksort( $map ); 304 return $map; 305 } 306 307 /** 308 * Compare old and new hash maps. 309 * 310 * @param array $old Old. 311 * @param array $new New. 312 * @return array 313 */ 314 protected static function compare_hashes( $old, $new ) { 315 $old = is_array( $old ) ? $old : array(); 316 $new = is_array( $new ) ? $new : array(); 317 318 $added = array_diff_key( $new, $old ); 319 $removed = array_diff_key( $old, $new ); 320 $modified = array(); 321 322 $common = array_intersect_key( $new, $old ); 323 foreach ( $common as $file => $hash ) { 324 if ( $old[ $file ] !== $hash ) { 325 $modified[ $file ] = $hash; 326 } 327 } 328 329 return array( 330 'added' => array_keys( $added ), 331 'modified' => array_keys( $modified ), 332 'removed' => array_keys( $removed ), 333 ); 334 } 335 336 /** 337 * Hardening checks. 338 * 339 * @return array 340 */ 341 protected static function hardening_checks() { 342 // Generic on/off for auto-updates by type (does not hook into filters). 343 $auto_plugins = function_exists( 'wp_is_auto_update_enabled_for_type' ) ? (bool) wp_is_auto_update_enabled_for_type( 'plugin' ) : false; 344 $xmlrpc_on = apply_filters( 'xmlrpc_enabled', true ); 345 346 return array( 347 'disallow_file_edit' => defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT, 348 'force_ssl_admin' => defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN, 349 'plugin_updates_enabled'=> $auto_plugins, 350 'xmlrpc_enabled' => (bool) $xmlrpc_on, 351 ); 162 echo '<button class="button ' . ( 'ssa_run_scan' === $action ? 'button-primary' : '' ) . '" type="submit"' . disabled( $disabled, true, false ) . '>' . esc_html( $label ) . '</button></form>'; 352 163 } 353 164 } 354 355 165 Siteseau_Site_Security_Auditor::init(); 356 357 endif;
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)