PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 3.0.3
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v3.0.3
3.0.5 3.0.4 3.0.3 3.0.2 3.0.1 3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 All 93 releases
← All changes | admin/class-admin.php +226 -39 3.0.2 → 3.0.3 View file →
@@ -1829,9 +1829,8 @@
1829 1829 'scanComplete' => __( 'Scan complete', 'vigilante' ),
1830 1830 'loading' => __( 'Loading...', 'vigilante' ),
1831 1831 'searching' => __( 'Searching...', 'vigilante' ),
1832 1832 'noUsersFound' => __( 'No users found', 'vigilante' ),
1833 - 'searchError' => __( 'Error searching users', 'vigilante' ),
1834 1833 'sending' => __( 'Sending...', 'vigilante' ),
1835 1834 'sendNotification' => __( 'Send notification now', 'vigilante' ),
1836 1835 'notificationsSent' => __( 'notifications sent', 'vigilante' ),
1837 1836 'skipped' => __( 'skipped', 'vigilante' ),
@@ -1850,9 +1849,9 @@
1850 1849 'modified' => __( 'Modified', 'vigilante' ),
1851 1850 'suspicious' => __( 'Suspicious', 'vigilante' ),
1852 1851 'totalScanned' => __( 'Total Scanned', 'vigilante' ),
1853 1852 'suspiciousFiles' => __( 'Suspicious Files', 'vigilante' ),
1854 - 'suspiciousWarning' => __( 'These files may contain malicious code or are in unexpected locations. Review immediately!', 'vigilante' ),
1853 + 'suspiciousWarning' => __( 'Files that can run code where none is expected, or that contain code written the way malware hides itself. Real infections show up here, and so do harmless files that plugins generate. Check the path and the reason before acting.', 'vigilante' ),
1855 1854 'file' => __( 'File', 'vigilante' ),
1856 1855 'reason' => __( 'Reason', 'vigilante' ),
1857 1856 'type' => __( 'Type', 'vigilante' ),
1858 1857 'unknown' => __( 'Unknown', 'vigilante' ),
@@ -1861,13 +1860,13 @@
1861 1860 'logWhatItMeans' => __( 'What it means', 'vigilante' ),
1862 1861 'logWhatToDo' => __( 'What to do', 'vigilante' ),
1863 1862 'logSelfSeeDetails' => __( 'Open File Integrity for the full detail', 'vigilante' ),
1864 1863 'modifiedFiles' => __( 'Modified Files', 'vigilante' ),
1865 - 'modifiedDescription' => __( 'These files (apparently) differ from the original WordPress or plugin versions.', 'vigilante' ),
1864 + 'modifiedDescription' => __( 'These files do not match the copy WordPress.org publishes for the installed version.', 'vigilante' ),
1866 1865 'extraFiles' => __( 'Extra Files', 'vigilante' ),
1867 1866 'extra' => __( 'Extra', 'vigilante' ),
1868 1867 'ignored' => __( 'Ignored', 'vigilante' ),
1869 - 'extraDescription' => __( 'PHP files found in plugins or themes that are not part of the original distribution from WordPress.org.', 'vigilante' ),
1868 + 'extraDescription' => __( 'Files that are not part of WordPress, or of the plugin or theme they sit in. Many are legitimate, and an injected file looks the same from here, so confirm where each one comes from.', 'vigilante' ),
1870 1869 'actions' => __( 'Actions', 'vigilante' ),
1871 1870 'ignore' => __( 'Ignore', 'vigilante' ),
1872 1871 'ignoring' => __( 'Ignoring...', 'vigilante' ),
1873 1872 'fileIgnored' => __( 'File added to ignored list.', 'vigilante' ),
@@ -1875,9 +1874,8 @@
1875 1874 'confirmClearIgnored' => __( 'Remove all files from the ignored list? They will appear in scan results again.', 'vigilante' ),
1876 1875 'ignoredCleared' => __( 'Ignored files list cleared. Page will reload...', 'vigilante' ),
1877 1876 'selectAll' => __( 'Select all', 'vigilante' ),
1878 1877 'bulkIgnoreSelected' => __( 'Ignore selected', 'vigilante' ),
1879 - 'bulkUnignoreSelected'=> __( 'Stop ignoring selected', 'vigilante' ),
1880 1878 'bulkNoSelection' => __( 'Select at least one file first.', 'vigilante' ),
1881 1879 'bulkConfirmIgnore' => __( 'Ignore the selected files? They will be hidden from future scan results until you remove them from the ignored list.', 'vigilante' ),
1882 1880 'bulkConfirmUnignore' => __( 'Remove the selected files from the ignored list? They will appear in scan results again.', 'vigilante' ),
1883 1881 'bulkProcessing' => __( 'Processing...', 'vigilante' ),
@@ -1882,11 +1880,11 @@
1882 1880 'bulkConfirmUnignore' => __( 'Remove the selected files from the ignored list? They will appear in scan results again.', 'vigilante' ),
1883 1881 'bulkProcessing' => __( 'Processing...', 'vigilante' ),
1884 1882 /* translators: %d: number of files selected for bulk action. */
1885 1883 'bulkSelectedCount' => __( '%d selected', 'vigilante' ),
1886 - 'allClear' => __( 'All files verified - no issues found!', 'vigilante' ),
1884 + 'allClear' => __( 'Good job! Nothing to report in the files that were checked.', 'vigilante' ),
1887 1885 'criticalConfigTitle' => __( 'Critical config files modified', 'vigilante' ),
1888 - 'criticalConfigDesc' => __( 'These files are common targets for code injection. Review the changes and approve if they are legitimate. Vigilant\'s own blocks are excluded from this check.', 'vigilante' ),
1886 + 'criticalConfigDesc' => __( 'These files changed since you last approved them. They are a common target for code injection, and they are also rewritten by cache, security and redirection plugins, often on every update. Review the changes and approve them if they belong to something you use: approving records the current content as the reference and does not touch the file. Vigilant\'s own blocks are excluded from this check, and Vigilant never restores these files by itself.', 'vigilante' ),
1889 1887 'approve' => __( 'Approve', 'vigilante' ),
1890 1888 'approving' => __( 'Approving...', 'vigilante' ),
1891 1889 'approvalLockedNotice' => $this->critical_approval_notice(),
1892 1890 'criticalApproved' => __( 'Change approved. Next scan will use the current state as baseline.', 'vigilante' ),
@@ -1979,13 +1977,10 @@
1979 1977 'noLogsToExport' => __( 'No logs to export', 'vigilante' ),
1980 1978 'exportFailed' => __( 'Export failed', 'vigilante' ),
1981 1979 'exportLogs' => __( 'Export Logs', 'vigilante' ),
1982 1980 // Backup strings
1983 - 'backupCreated' => __( 'Backup created successfully.', 'vigilante' ),
1984 1981 'createBackupNow' => __( 'Download Backup', 'vigilante' ),
1985 1982 'downloadBackup' => __( 'Download Backup (.zip)', 'vigilante' ),
1986 - /* translators: %d: number of tables */
1987 - 'tablesCount' => __( '%d tables', 'vigilante' ),
1988 1983 /* translators: 1: table count, 2: human-readable size */
1989 1984 'dbTablesTotal' => __( '%1$d tables total (%2$s)', 'vigilante' ),
1990 1985 /* translators: 1: selected count, 2: human-readable size */
1991 1986 'dbTablesSelected' => __( '%1$d tables selected (%2$s)', 'vigilante' ),
@@ -1992,9 +1987,8 @@
1992 1987 // Settings search strings
1993 1988 'searchNoResults' => __( 'No matching settings found.', 'vigilante' ),
1994 1989 /* translators: %d: number of results that did not fit in the list. */
1995 1990 'searchMoreResults' => __( '%d more results. Refine the search to see them.', 'vigilante' ),
1996 - 'searchInTab' => __( 'in', 'vigilante' ),
1997 1991 // Modules string
1998 1992 /* translators: 1: enabled count, 2: total count */
1999 1993 'modulesEnabled' => __( '%1$d / %2$d modules enabled', 'vigilante' ),
2000 1994 // Activity log label maps for JS rendering
@@ -1999,8 +1993,9 @@
1999 1993 'modulesEnabled' => __( '%1$d / %2$d modules enabled', 'vigilante' ),
2000 1994 // Activity log label maps for JS rendering
2001 1995 'eventTypeLabels' => array(
2002 1996 'login' => __( 'Login', 'vigilante' ),
1997 + '2fa' => __( 'Two-factor', 'vigilante' ),
2003 1998 'user' => __( 'User', 'vigilante' ),
2004 1999 'content' => __( 'Content', 'vigilante' ),
2005 2000 'plugin' => __( 'Plugin', 'vigilante' ),
2006 2001 'theme' => __( 'Theme', 'vigilante' ),
@@ -2066,10 +2061,8 @@
2066 2061 /* translators: 1: first item number, 2: last item number, 3: total items */
2067 2062 'paginationOf' => __( '%1$d–%2$d of %3$d', 'vigilante' ),
2068 2063 'paginationEmpty' => __( '0 items', 'vigilante' ),
2069 2064 // Security Analyzer strings
2070 - 'analyzerScanNow' => __( 'Scan now', 'vigilante' ),
2071 - 'analyzerScanning' => __( 'Scanning…', 'vigilante' ),
2072 2065 'analyzerFastPhase' => __( 'Running fast checks…', 'vigilante' ),
2073 2066 'analyzerSlowPhase' => __( 'Running remote checks…', 'vigilante' ),
2074 2067 'analyzerScanComplete' => __( 'Security scan complete.', 'vigilante' ),
2075 2068 'analyzerScanFailed' => __( 'Security scan failed.', 'vigilante' ),
@@ -2075,11 +2068,8 @@
2075 2068 'analyzerScanFailed' => __( 'Security scan failed.', 'vigilante' ),
2076 2069 'analyzerShowDetails' => __( 'Show detailed breakdown', 'vigilante' ),
2077 2070 'analyzerHideDetails' => __( 'Hide detailed breakdown', 'vigilante' ),
2078 2071 'analyzerGoToSetting' => __( 'Go to setting', 'vigilante' ),
2079 - 'analyzerNoData' => __( 'No data yet — run a scan to populate this category.', 'vigilante' ),
2080 - 'analyzerJustNow' => __( 'just now', 'vigilante' ),
2081 - 'analyzerAgo' => __( 'ago', 'vigilante' ),
2082 2072 'analyzerSettingsSaved' => __( 'Analyzer settings saved.', 'vigilante' ),
2083 2073 'analyzerLastScanJustNow' => __( 'Last scan just now', 'vigilante' ),
2084 2074 'analyzerQualityExcellent' => __( 'Excellent', 'vigilante' ),
2085 2075 'analyzerQualityGood' => __( 'Good', 'vigilante' ),
@@ -2085,9 +2075,8 @@
2085 2075 'analyzerQualityGood' => __( 'Good', 'vigilante' ),
2086 2076 'analyzerQualityFair' => __( 'Fair', 'vigilante' ),
2087 2077 'analyzerQualityPoor' => __( 'Poor', 'vigilante' ),
2088 2078 'analyzerQualityCritical' => __( 'Critical', 'vigilante' ),
2089 - 'analyzerPts' => __( 'pts', 'vigilante' ),
2090 2079 'analyzerLearnMore' => __( 'Learn more', 'vigilante' ),
2091 2080 'analyzerInfoAllClear' => __( 'All clear', 'vigilante' ),
2092 2081 /* translators: %d: number of findings in an info-only category */
2093 2082 'analyzerInfoFindings' => __( '%d findings', 'vigilante' ),
@@ -3741,8 +3730,9 @@
3741 3730 <label>
3742 3731 <input type="checkbox" name="firewall[block_bad_bots]" value="1" <?php disabled( $vg_main_locked ); ?> <?php checked( ! empty( $options['block_bad_bots'] ) ); ?>>
3743 3732 <?php esc_html_e( 'Block known malicious bots and scanners', 'vigilante' ); ?>
3744 3733 </label>
3734 + <p class="description"><?php esc_html_e( 'The list also stops SEO crawlers such as Ahrefs, Semrush, Majestic and Screaming Frog, and command line clients such as curl, wget and python-requests. To let one of them through, add its name to the User-Agent Whitelist below. On Apache part of the list is applied in .htaccess, before WordPress loads, so those requests get a 403 and do not appear in the Security Audit.', 'vigilante' ); ?></p>
3745 3735 </td>
3746 3736 </tr>
3747 3737 </table>
3748 3738
@@ -4055,9 +4045,9 @@
4055 4045 <th scope="row"><?php esc_html_e( 'Limit HTTP Methods', 'vigilante' ); ?></th>
4056 4046 <td>
4057 4047 <label>
4058 4048 <input type="checkbox" name="firewall[limit_http_methods]" value="1" <?php checked( ! empty( $options['limit_http_methods'] ) ); ?>>
4059 - <?php esc_html_e( 'Allow only GET, POST, HEAD (blocks PUT, DELETE, TRACE, etc.)', 'vigilante' ); ?>
4049 + <?php esc_html_e( 'Block the HTTP methods WordPress does not use (TRACE, TRACK, CONNECT and any unknown one). GET, POST, HEAD, OPTIONS, PUT, PATCH and DELETE keep working.', 'vigilante' ); ?>
4060 4050 </label>
4061 4051 </td>
4062 4052 </tr>
4063 4053 </table>
@@ -6338,9 +6328,9 @@
6338 6328 <select id="vigilante-f-audit-alerts-immediate-min-severity" name="audit_alerts[immediate][min_severity]">
6339 6329 <option value="critical" <?php selected( $alert_severity, 'critical' ); ?>><?php esc_html_e( 'Critical only (recommended)', 'vigilante' ); ?></option>
6340 6330 <option value="warning" <?php selected( $alert_severity, 'warning' ); ?>><?php esc_html_e( 'Warning and Critical', 'vigilante' ); ?></option>
6341 6331 </select>
6342 - <p class="description"><?php esc_html_e( 'A new administrator, a closed plugin or a privilege escalation are all logged as Critical, so "Critical only" already covers them.', 'vigilante' ); ?></p>
6332 + <p class="description"><?php esc_html_e( 'A closed plugin is logged as Critical, so "Critical only" covers it. A new administrator and a change of role are logged as Warning: they have their own emails, in Users > Admin monitoring, and reach this alert only with "Warning and Critical".', 'vigilante' ); ?></p>
6343 6333 </td>
6344 6334 </tr>
6345 6335 <tr id="field-audit-alerts-threshold">
6346 6336 <th scope="row"><?php esc_html_e( 'Threshold alerts', 'vigilante' ); ?></th>
@@ -6433,8 +6423,9 @@
6433 6423
6434 6424 // Label maps for translated display
6435 6425 $type_labels = array(
6436 6426 'login' => __( 'Login', 'vigilante' ),
6427 + '2fa' => __( 'Two-factor', 'vigilante' ),
6437 6428 'user' => __( 'User', 'vigilante' ),
6438 6429 'content' => __( 'Content', 'vigilante' ),
6439 6430 'plugin' => __( 'Plugin', 'vigilante' ),
6440 6431 'theme' => __( 'Theme', 'vigilante' ),
@@ -6463,8 +6454,9 @@
6463 6454 <input type="text" id="vigilante-log-search" aria-label="<?php esc_attr_e( 'Search the activity log', 'vigilante' ); ?>" size="1" placeholder="<?php esc_attr_e( 'Search logs (min. 3 characters)...', 'vigilante' ); ?>" class="vigilante-log-search-input">
6464 6455 <select id="vigilante-log-type-filter" aria-label="<?php esc_attr_e( 'Filter the log by event type', 'vigilante' ); ?>">
6465 6456 <option value=""><?php esc_html_e( 'All Types', 'vigilante' ); ?></option>
6466 6457 <option value="login"><?php esc_html_e( 'Login', 'vigilante' ); ?></option>
6458 + <option value="2fa"><?php esc_html_e( 'Two-factor', 'vigilante' ); ?></option>
6467 6459 <option value="user"><?php esc_html_e( 'User', 'vigilante' ); ?></option>
6468 6460 <option value="content"><?php esc_html_e( 'Content', 'vigilante' ); ?></option>
6469 6461 <option value="plugin"><?php esc_html_e( 'Plugin', 'vigilante' ); ?></option>
6470 6462 <option value="theme"><?php esc_html_e( 'Theme', 'vigilante' ); ?></option>
@@ -7008,8 +7000,49 @@
7008 7000 </details>
7009 7001 <?php
7010 7002 }
7011 7003
7004 + /**
7005 + * Print what each kind of finding in a list usually is and what to do
7006 + *
7007 + * One line per kind actually present in the list, from the single source
7008 + * of that text, Vigilante_File_Integrity::finding_guidance().
7009 + *
7010 + * @param array $items Findings of one section of the last scan.
7011 + */
7012 + private function render_fi_guidance( $items ) {
7013 + $seen = array();
7014 + $guides = array();
7015 +
7016 + foreach ( (array) $items as $item ) {
7017 + $type = is_array( $item ) ? (string) ( $item['type'] ?? '' ) : '';
7018 +
7019 + if ( '' === $type || 'vigilante_self' === $type || isset( $seen[ $type ] ) ) {
7020 + continue;
7021 + }
7022 + $seen[ $type ] = true;
7023 +
7024 + $guide = Vigilante_File_Integrity::finding_guidance( $type );
7025 +
7026 + if ( '' !== $guide['advice'] ) {
7027 + // A plugin and a theme share their text: one line for both,
7028 + // under the two names.
7029 + $guides[ $guide['advice'] ][] = $guide['label'];
7030 + }
7031 + }
7032 +
7033 + if ( empty( $guides ) ) {
7034 + return;
7035 + }
7036 + ?>
7037 + <ul class="vigilante-fi-guidance" style="margin: 8px 0 12px 18px; list-style: disc;">
7038 + <?php foreach ( $guides as $advice => $labels ) : ?>
7039 + <li><strong><?php echo esc_html( implode( ', ', $labels ) ); ?>:</strong> <?php echo esc_html( $advice ); ?></li>
7040 + <?php endforeach; ?>
7041 + </ul>
7042 + <?php
7043 + }
7044 +
7012 7045 private function render_tab_file_integrity() {
7013 7046 $is_disabled = $this->render_module_disabled_notice( 'file_integrity' );
7014 7047 $options = $this->settings->get_section( 'file_integrity' );
7015 7048 // On the main site of a network the critical-file scan is the network's
@@ -7081,9 +7114,10 @@
7081 7114 <h2>
7082 7115 <?php esc_html_e( 'File Integrity Monitoring', 'vigilante' ); ?>
7083 7116 <span class="vigilante-method-badge php"><?php esc_html_e( 'PHP', 'vigilante' ); ?></span>
7084 7117 </h2>
7085 - <p><?php esc_html_e( 'Detects file modifications using WordPress.org checksums.', 'vigilante' ); ?></p>
7118 + <p><?php esc_html_e( 'Compares WordPress, and the plugins and themes that come from WordPress.org, with the official copy of each file, looks for code where there should be none (uploads and the site root) and watches wp-config.php and .htaccess for changes.', 'vigilante' ); ?></p>
7119 + <p class="description"><?php esc_html_e( 'What it cannot compare: plugins and themes that are not on WordPress.org, must-use plugins, drop-ins (advanced-cache.php, object-cache.php and the like) and the other folders of wp-content have no official copy. They are searched for hidden code, which finds malware that disguises itself and misses most code written in the open. The results name what could not be compared.', 'vigilante' ); ?></p>
7086 7120
7087 7121 <table class="form-table">
7088 7122 <tr>
7089 7123 <th scope="row"><?php esc_html_e( 'Automatic Scans', 'vigilante' ); ?></th>
@@ -7106,13 +7140,13 @@
7106 7140 <tr>
7107 7141 <th scope="row"><label for="vigilante-f-file-integrity-notify-level"><?php esc_html_e( 'Email Notifications', 'vigilante' ); ?></label></th>
7108 7142 <td>
7109 7143 <select id="vigilante-f-file-integrity-notify-level" name="file_integrity[notify_level]">
7110 - <option value="all" <?php selected( $notify_level, 'all' ); ?>><?php esc_html_e( 'All issues (modified + suspicious)', 'vigilante' ); ?></option>
7111 - <option value="suspicious_only" <?php selected( $notify_level, 'suspicious_only' ); ?>><?php esc_html_e( 'Suspicious files only', 'vigilante' ); ?></option>
7144 + <option value="all" <?php selected( $notify_level, 'all' ); ?>><?php esc_html_e( 'Every finding, modified files included', 'vigilante' ); ?></option>
7145 + <option value="suspicious_only" <?php selected( $notify_level, 'suspicious_only' ); ?>><?php esc_html_e( 'Serious findings only', 'vigilante' ); ?></option>
7112 7146 <option value="disabled" <?php selected( $notify_level, 'disabled' ); ?>><?php esc_html_e( 'Disabled', 'vigilante' ); ?></option>
7113 7147 </select>
7114 - <p class="description"><?php esc_html_e( '"Suspicious files only" reduces noise by skipping modified file notifications. Recommended for most sites.', 'vigilante' ); ?></p>
7148 + <p class="description"><?php esc_html_e( 'Serious findings are suspicious and extra files, changes in wp-config.php or .htaccess and closed plugins. That is the recommended choice: a file that is only modified is rarely more than an update that did not finish. The email goes out when a scan finds something new: a finding you were already told about is not sent again, unless the file changes once more.', 'vigilante' ); ?></p>
7115 7149 </td>
7116 7150 </tr>
7117 7151 <tr>
7118 7152 <th scope="row"><?php esc_html_e( 'Instant Alert', 'vigilante' ); ?></th>
@@ -7118,11 +7152,11 @@
7118 7152 <th scope="row"><?php esc_html_e( 'Instant Alert', 'vigilante' ); ?></th>
7119 7153 <td>
7120 7154 <label>
7121 7155 <input type="checkbox" name="file_integrity[instant_alert]" value="1" <?php checked( ! empty( $options['instant_alert'] ) ); ?>>
7122 - <?php esc_html_e( 'Send immediate alert when modified, suspicious or additional files are detected, or when a closed plugin is found', 'vigilante' ); ?>
7156 + <?php esc_html_e( 'Email every finding when a scan ends, modified files included, and email a closed plugin as soon as the daily check finds it', 'vigilante' ); ?>
7123 7157 </label>
7124 - <p class="description"><?php esc_html_e( 'Fires even if the Email Notifications setting above is set to Disabled.', 'vigilante' ); ?></p>
7158 + <p class="description"><?php esc_html_e( 'This takes precedence over the choice above, Disabled included.', 'vigilante' ); ?></p>
7125 7159 <p class="description">
7126 7160 <?php
7127 7161 printf(
7128 7162 /* translators: %s: Link to notification settings */
@@ -7148,29 +7182,29 @@
7148 7182 <td>
7149 7183 <fieldset>
7150 7184 <label>
7151 7185 <input type="checkbox" name="file_integrity[scan_core]" value="1" <?php checked( $options['scan_core'] ?? true ); ?>>
7152 - <?php esc_html_e( 'Core files (compare against WordPress.org checksums)', 'vigilante' ); ?>
7186 + <?php esc_html_e( 'WordPress core (compared with WordPress.org) and the other files in the site root', 'vigilante' ); ?>
7153 7187 </label>
7154 7188 <br>
7155 7189 <label>
7156 7190 <input type="checkbox" name="file_integrity[scan_plugins]" value="1" <?php checked( $options['scan_plugins'] ?? true ); ?>>
7157 - <?php esc_html_e( 'Plugins (WordPress.org repository plugins)', 'vigilante' ); ?>
7191 + <?php esc_html_e( 'Plugins (compared with WordPress.org; the rest, and the other folders of wp-content, are only searched for hidden code)', 'vigilante' ); ?>
7158 7192 </label>
7159 7193 <br>
7160 7194 <label>
7161 7195 <input type="checkbox" name="file_integrity[scan_themes]" value="1" <?php checked( $options['scan_themes'] ?? true ); ?>>
7162 - <?php esc_html_e( 'Themes (WordPress.org repository themes)', 'vigilante' ); ?>
7196 + <?php esc_html_e( 'Themes (compared with WordPress.org; the rest are only searched for hidden code)', 'vigilante' ); ?>
7163 7197 </label>
7164 7198 <br>
7165 7199 <label>
7166 7200 <input type="checkbox" name="file_integrity[scan_uploads]" value="1" <?php checked( $options['scan_uploads'] ?? true ); ?>>
7167 - <?php esc_html_e( 'Uploads directory (detect PHP files, double extensions, .htaccess)', 'vigilante' ); ?>
7201 + <?php esc_html_e( 'Uploads directory (PHP and other scripts, double extensions, .htaccess and PHP configuration files)', 'vigilante' ); ?>
7168 7202 </label>
7169 7203 <br>
7170 7204 <label>
7171 7205 <input type="checkbox" name="file_integrity[scan_critical_config]" value="1" <?php disabled( $vg_main_locked ); ?> <?php checked( $options['scan_critical_config'] ?? true ); ?>>
7172 - <?php esc_html_e( 'Critical config files (wp-config.php, .htaccess baseline monitoring)', 'vigilante' ); ?>
7206 + <?php esc_html_e( 'wp-config.php and .htaccess (any change since you last approved them)', 'vigilante' ); ?>
7173 7207 <?php if ( $vg_main_locked ) : ?>
7174 7208 <span class="description" style="display:block;margin-left:24px;"><?php echo esc_html( Vigilante_Settings::get_shared_files_notice() ); ?></span>
7175 7209 <?php endif; ?>
7176 7210 </label>
@@ -7184,10 +7218,10 @@
7184 7218 </tr>
7185 7219 <tr>
7186 7220 <th scope="row"><label for="vigilante-f-file-integrity-excluded-paths"><?php esc_html_e( 'Excluded Paths', 'vigilante' ); ?></label></th>
7187 7221 <td>
7188 - <textarea id="vigilante-f-file-integrity-excluded-paths" name="file_integrity[excluded_paths]" rows="4" class="large-text code" placeholder="wp-content/cache&#10;wp-content/languages"><?php echo esc_textarea( implode( "\n", $options['excluded_paths'] ?? array() ) ); ?></textarea>
7189 - <p class="description"><?php esc_html_e( 'One path per line, relative to the WordPress root. A path such as wp-content/cache excludes exactly that folder and everything under it. A name on its own, such as cache, excludes any folder called exactly that, wherever it is.', 'vigilante' ); ?></p>
7222 + <textarea id="vigilante-f-file-integrity-excluded-paths" name="file_integrity[excluded_paths]" rows="4" class="large-text code" placeholder="wp-content/backups"><?php echo esc_textarea( implode( "\n", $options['excluded_paths'] ?? array() ) ); ?></textarea>
7223 + <p class="description"><?php esc_html_e( 'One path per line, relative to the WordPress root. A path such as wp-content/backups excludes exactly that folder and everything under it. A name on its own, such as backups, excludes any folder called exactly that, wherever it is. Nothing in an excluded folder is looked at, so keep the list short.', 'vigilante' ); ?></p>
7190 7224 </td>
7191 7225 </tr>
7192 7226 <tr>
7193 7227 <th scope="row"><label for="vigilante-f-file-integrity-excluded-extensions"><?php esc_html_e( 'Excluded Extensions', 'vigilante' ); ?></label></th>
@@ -7230,11 +7264,49 @@
7230 7264 <div id="vigilante-scan-results" class="vigilante-settings-section" style="display:none;"></div>
7231 7265
7232 7266 <?php $this->render_self_protection_box( $options ); ?>
7233 7267
7268 + <?php
7269 + // Whole folders of code left out of every scan. A partial scan tells
7270 + // how to check the rest by excluding what it did check, and those
7271 + // lines have to come out afterwards: with them in place a scan comes
7272 + // out complete and clean without having looked there. Said for as
7273 + // long as they are in the list, whoever put them there and why.
7274 + $vg_fi_tool = new Vigilante_File_Integrity( $this->settings, $this->database );
7275 + $vg_excluded_code = $vg_fi_tool->excluded_code_folders();
7276 + $vg_folders_to_skip = $vg_fi_tool->folders_to_exclude( $last_results );
7277 + ?>
7278 + <?php if ( ! empty( $vg_excluded_code ) ) : ?>
7279 + <div id="vigilante-fi-excluded-code" class="notice notice-warning inline">
7280 + <p>
7281 + <strong>
7282 + <?php
7283 + printf(
7284 + /* translators: %s: Comma separated list of folders */
7285 + esc_html__( 'Excluded Paths holds whole folders of plugins or themes, or the uploads folder: %s.', 'vigilante' ),
7286 + esc_html( implode( ', ', $vg_excluded_code ) )
7287 + );
7288 + ?>
7289 + </strong>
7290 + <?php esc_html_e( 'No scan looks inside them, the scheduled one included, and a scan can come out complete and clean without having checked them. If you added them to check the rest of a large site, take them out once that scan is done.', 'vigilante' ); ?>
7291 + </p>
7292 + </div>
7293 + <?php endif; ?>
7294 +
7234 7295 <?php if ( $last_scan || $has_closed || $closed_last_check > 0 ) : ?>
7235 7296 <div id="vigilante-section-fi-last-scan" class="vigilante-settings-section">
7236 7297 <h2><?php esc_html_e( 'Last Scan Results', 'vigilante' ); ?></h2>
7298 + <?php
7299 + // A scan the server stops halfway stores nothing, and neither
7300 + // does one WordPress never starts: the tab kept showing the
7301 + // last result that did finish, with nothing to say that scans
7302 + // had stopped. Its date is the only sign.
7303 + if ( $last_scan && $this->settings->is_module_enabled( 'file_integrity' ) && Vigilante_File_Integrity::is_scan_overdue( $last_scan, $options['scan_frequency'] ?? 'daily' ) ) :
7304 + ?>
7305 + <div class="notice notice-warning inline">
7306 + <p><strong><?php esc_html_e( 'Scans are scheduled, and none has finished since the date below.', 'vigilante' ); ?></strong> <?php esc_html_e( 'Either WordPress is not running its scheduled tasks, or the server stops the scan before it ends, and a scan that is stopped stores no result. What you see here is old. Run a scan now: if it finishes, the scheduled tasks are what is failing. If it does not, the site is too large for the time the server gives a request, and adding large folders that do not need checking to Excluded Paths leaves more time for the rest.', 'vigilante' ); ?></p>
7307 + </div>
7308 + <?php endif; ?>
7237 7309 <?php if ( $last_scan ) : ?>
7238 7310 <p>
7239 7311 <?php
7240 7312 // Build the "X files scanned" hint inline with the date so it doesn't
@@ -7317,12 +7389,112 @@
7317 7389 </div>
7318 7390 <?php endif; ?>
7319 7391 </div>
7320 7392
7393 + <?php
7394 + // A scan that could not finish, or could not get the official
7395 + // copy to compare with, checked less than it seems. Until now
7396 + // its errors were stored and never shown, and the tab could
7397 + // say that every file had passed.
7398 + $vg_scan_errors = ( $last_results && ! empty( $last_results['errors'] ) && is_array( $last_results['errors'] ) ) ? $last_results['errors'] : array();
7399 + $vg_scan_partial = ! empty( $vg_scan_errors ) || ( $last_results && ! empty( $last_results['incomplete'] ) );
7400 +
7401 + // Files of the official copies that are not on the server. Core
7402 + // files under wp-content are left out: results stored before
7403 + // the bundled themes and plugins got their own turn list
7404 + // there the ones people delete on purpose.
7405 + $vg_missing_core = array();
7406 + if ( $last_results && ! empty( $last_results['missing'] ) && is_array( $last_results['missing'] ) ) {
7407 + foreach ( $last_results['missing'] as $item ) {
7408 + if ( ! is_array( $item ) || ! isset( $item['file'] ) ) {
7409 + continue;
7410 + }
7411 +
7412 + $vg_missing_type = (string) ( $item['type'] ?? '' );
7413 +
7414 + if ( in_array( $vg_missing_type, array( 'plugin', 'theme' ), true )
7415 + || ( 'core' === $vg_missing_type && 0 !== strpos( (string) $item['file'], 'wp-content/' ) ) ) {
7416 + $vg_missing_core[] = (string) $item['file'];
7417 + }
7418 + }
7419 + }
7420 + ?>
7421 +
7422 + <?php if ( $last_results && ! empty( $last_results['unverified'] ) && is_array( $last_results['unverified'] ) ) : ?>
7423 + <p class="description">
7424 + <?php
7425 + printf(
7426 + /* translators: %s: Comma separated list of plugin and theme names */
7427 + esc_html__( 'No official copy to compare with, so only searched for hidden code: %s.', 'vigilante' ),
7428 + esc_html( implode( ', ', array_unique( array_map( 'strval', $last_results['unverified'] ) ) ) )
7429 + );
7430 + ?>
7431 + </p>
7432 + <?php endif; ?>
7433 +
7434 + <?php
7435 + // What the scan did not get to, by name. A scan starts from
7436 + // the beginning every time, so on a site too large for it
7437 + // these are the same on every scan, and whoever reads the
7438 + // result has to know that nothing here was looked at.
7439 + $vg_scan_notes = array_merge( $vg_scan_errors, Vigilante_File_Integrity::not_checked_lines( $last_results ) );
7440 + ?>
7441 + <?php if ( $vg_scan_partial ) : ?>
7442 + <div class="notice notice-warning inline">
7443 + <p><strong><?php esc_html_e( 'The last scan could not check everything, so its result is partial.', 'vigilante' ); ?></strong></p>
7444 + <?php if ( ! empty( $vg_scan_notes ) ) : ?>
7445 + <ul style="margin: 0 0 8px 18px; list-style: disc;">
7446 + <?php foreach ( $vg_scan_notes as $vg_scan_note ) : ?>
7447 + <li><?php echo esc_html( (string) $vg_scan_note ); ?></li>
7448 + <?php endforeach; ?>
7449 + </ul>
7450 + <?php endif; ?>
7451 + <p>
7452 + <?php
7453 + printf(
7454 + /* translators: %d: Seconds a scan runs for at most */
7455 + esc_html__( 'Run the scan again: a second scan is faster, because what it downloads from WordPress.org is kept for a while. If the result is still partial, either the server cannot reach WordPress.org or the site is larger than a scan can check in its %d seconds, and then every scan leaves the same part unchecked. Adding large folders that do not need checking to Excluded Paths leaves more time for the rest.', 'vigilante' ),
7456 + (int) Vigilante_File_Integrity::MAX_SCAN_SECONDS
7457 + );
7458 + ?>
7459 + </p>
7460 + <?php if ( ! empty( $vg_folders_to_skip ) ) : ?>
7461 + <p><strong><?php esc_html_e( 'To check what was left out:', 'vigilante' ); ?></strong></p>
7462 + <ol style="margin: 0 0 8px 18px;">
7463 + <li><?php esc_html_e( 'Deal first with what this scan found in the plugins and themes it did check: the next result will not list it. Then add the lines below, which are their folders, to Excluded Paths, higher up on this page, and save.', 'vigilante' ); ?></li>
7464 + <li><?php esc_html_e( 'Press Run Scan Now. The scan spends its time on the rest. If it comes out partial again, repeat with the lines it gives then.', 'vigilante' ); ?></li>
7465 + <li><?php esc_html_e( 'Take those lines out of Excluded Paths. While they are there no scan looks inside those folders, the scheduled one included.', 'vigilante' ); ?></li>
7466 + </ol>
7467 + <textarea id="vigilante-fi-folders-to-exclude" readonly rows="<?php echo (int) min( 12, max( 3, count( $vg_folders_to_skip ) ) ); ?>" class="large-text code" style="margin-bottom: 8px;"><?php echo esc_textarea( implode( "\n", $vg_folders_to_skip ) ); ?></textarea>
7468 + <?php endif; ?>
7469 + </div>
7470 + <?php endif; ?>
7471 +
7472 + <?php if ( ! empty( $vg_missing_core ) ) : ?>
7473 + <div class="vigilante-file-list">
7474 + <h3><?php esc_html_e( 'Missing Files', 'vigilante' ); ?></h3>
7475 + <p class="description"><?php esc_html_e( 'These files are in the official copy and not on the server. Reinstall what they belong to (WordPress from Dashboard > Updates, a plugin or a theme from its own screen) and scan again.', 'vigilante' ); ?></p>
7476 + <ul style="margin: 8px 0 12px 18px; list-style: disc;">
7477 + <?php foreach ( array_slice( $vg_missing_core, 0, 20 ) as $vg_missing_file ) : ?>
7478 + <li><code><?php echo esc_html( $vg_missing_file ); ?></code></li>
7479 + <?php endforeach; ?>
7480 + </ul>
7481 + <?php if ( count( $vg_missing_core ) > 20 ) : ?>
7482 + <p class="description">
7483 + <?php
7484 + /* translators: %d: Number of additional files */
7485 + printf( esc_html__( '... and %d more', 'vigilante' ), (int) ( count( $vg_missing_core ) - 20 ) );
7486 + ?>
7487 + </p>
7488 + <?php endif; ?>
7489 + </div>
7490 + <?php endif; ?>
7491 +
7321 7492 <?php if ( ! empty( $last_results['suspicious'] ) ) : ?>
7322 7493 <div class="vigilante-file-list vigilante-suspicious-files vigilante-paginated-section" data-bulk-mode="ignore">
7323 7494 <h3 style="color: #d63638;"><?php esc_html_e( 'Suspicious Files', 'vigilante' ); ?></h3>
7324 - <p class="description" style="color: #d63638;"><?php esc_html_e( '&#9888; Warning: These files may contain malicious code or are in unexpected locations. Review immediately!', 'vigilante' ); ?></p>
7495 + <p class="description" style="color: #d63638;"><?php esc_html_e( '&#9888; Files that can run code where none is expected, or that contain code written the way malware hides itself. Real infections show up here, and so do harmless files that plugins generate. Check the path and the reason before acting.', 'vigilante' ); ?></p>
7496 + <?php $this->render_fi_guidance( $last_results['suspicious'] ); ?>
7325 7497 <div class="vigilante-fi-bulk-bar">
7326 7498 <button type="button" class="button vigilante-bulk-ignore" disabled><?php esc_html_e( 'Ignore selected', 'vigilante' ); ?></button>
7327 7499 <span class="vigilante-fi-bulk-count" aria-live="polite"></span>
7328 7500 </div>
@@ -7358,8 +7530,11 @@
7358 7530 $file_path = (string) $item;
7359 7531 }
7360 7532 if ( 'vigilante_self' === $file_type ) {
7361 7533 $file_type = __( 'Vigilant (self)', 'vigilante' );
7534 + } else {
7535 + // A name a person can read, instead of the internal slug.
7536 + $file_type = Vigilante_File_Integrity::finding_guidance( $file_type )['label'];
7362 7537 }
7363 7538 ?>
7364 7539 <tr>
7365 7540 <th scope="row" class="check-column"><input type="checkbox" class="vigilante-fi-cb" value="<?php echo esc_attr( $file_path ); ?>"></th>
@@ -7378,9 +7553,10 @@
7378 7553
7379 7554 <?php if ( ! empty( $last_results['extra'] ) ) : ?>
7380 7555 <div class="vigilante-file-list vigilante-extra-files vigilante-paginated-section" data-bulk-mode="ignore">
7381 7556 <h3 style="color: #b32d2e;"><?php esc_html_e( 'Extra Files', 'vigilante' ); ?></h3>
7382 - <p class="description"><?php esc_html_e( 'PHP files found in plugins or themes that are not part of the original distribution from WordPress.org. May be legitimate customizations or injected backdoors.', 'vigilante' ); ?></p>
7557 + <p class="description"><?php esc_html_e( 'Files that are not part of WordPress, or of the plugin or theme they sit in. Many are legitimate, and an injected file looks the same from here, so confirm where each one comes from.', 'vigilante' ); ?></p>
7558 + <?php $this->render_fi_guidance( $last_results['extra'] ); ?>
7383 7559 <div class="vigilante-fi-bulk-bar">
7384 7560 <button type="button" class="button vigilante-bulk-ignore" disabled><?php esc_html_e( 'Ignore selected', 'vigilante' ); ?></button>
7385 7561 <span class="vigilante-fi-bulk-count" aria-live="polite"></span>
7386 7562 </div>
@@ -7402,8 +7578,11 @@
7402 7578 $file_reason = is_array( $item ) ? ( $item['reason'] ?? __( 'Unknown', 'vigilante' ) ) : __( 'Unknown', 'vigilante' );
7403 7579 $file_type = is_array( $item ) ? ( $item['type'] ?? 'unknown' ) : 'unknown';
7404 7580 if ( 'vigilante_self' === $file_type ) {
7405 7581 $file_type = __( 'Vigilant (self)', 'vigilante' );
7582 + } else {
7583 + // A name a person can read, instead of the internal slug.
7584 + $file_type = Vigilante_File_Integrity::finding_guidance( $file_type )['label'];
7406 7585 }
7407 7586 ?>
7408 7587 <tr>
7409 7588 <th scope="row" class="check-column"><input type="checkbox" class="vigilante-fi-cb" value="<?php echo esc_attr( $file_path ); ?>"></th>
@@ -7452,9 +7631,9 @@
7452 7631 <?php if ( ! empty( $critical_modified ) ) : ?>
7453 7632 <div class="vigilante-file-list vigilante-critical-config-files">
7454 7633 <h3 style="color: #e36210;"><?php esc_html_e( 'Critical config files modified', 'vigilante' ); ?></h3>
7455 7634 <p class="description">
7456 - <?php esc_html_e( 'These files are common targets for code injection. Review the changes and approve if they are legitimate. Vigilant\'s own blocks are excluded from this check.', 'vigilante' ); ?>
7635 + <?php esc_html_e( 'These files changed since you last approved them. They are a common target for code injection, and they are also rewritten by cache, security and redirection plugins, often on every update. Review the changes and approve them if they belong to something you use: approving records the current content as the reference and does not touch the file. Vigilant\'s own blocks are excluded from this check, and Vigilant never restores these files by itself.', 'vigilante' ); ?>
7457 7636 </p>
7458 7637 <table class="wp-list-table widefat fixed striped">
7459 7638 <thead>
7460 7639 <tr>
@@ -7615,9 +7794,10 @@
7615 7794
7616 7795 <?php if ( ! empty( $regular_modified ) ) : ?>
7617 7796 <div class="vigilante-file-list vigilante-paginated-section" data-bulk-mode="ignore">
7618 7797 <h3><?php esc_html_e( 'Modified Files', 'vigilante' ); ?></h3>
7619 - <p class="description"><?php esc_html_e( 'These files (apparently) differ from the original WordPress or plugin versions.', 'vigilante' ); ?></p>
7798 + <p class="description"><?php esc_html_e( 'These files do not match the copy WordPress.org publishes for the installed version.', 'vigilante' ); ?></p>
7799 + <?php $this->render_fi_guidance( $regular_modified ); ?>
7620 7800 <div class="vigilante-fi-bulk-bar">
7621 7801 <button type="button" class="button vigilante-bulk-ignore" disabled><?php esc_html_e( 'Ignore selected', 'vigilante' ); ?></button>
7622 7802 <span class="vigilante-fi-bulk-count" aria-live="polite"></span>
7623 7803 </div>
@@ -7648,8 +7828,11 @@
7648 7828 $file_path = (string) $item;
7649 7829 }
7650 7830 if ( 'vigilante_self' === $file_type ) {
7651 7831 $file_type = __( 'Vigilant (self)', 'vigilante' );
7832 + } else {
7833 + // A name a person can read, instead of the internal slug.
7834 + $file_type = Vigilante_File_Integrity::finding_guidance( $file_type )['label'];
7652 7835 }
7653 7836 ?>
7654 7837 <tr>
7655 7838 <th scope="row" class="check-column"><input type="checkbox" class="vigilante-fi-cb" value="<?php echo esc_attr( $file_path ); ?>"></th>
@@ -7671,11 +7854,11 @@
7671 7854 // above, this line contradicted it (its findings no longer
7672 7855 // travel in the tables below).
7673 7856 $vg_self_alarm = in_array( $this->self_integrity_summary( $options )['tone'], array( 'critical', 'off', 'warning' ), true );
7674 7857 ?>
7675 - <?php if ( $last_results && ! $vg_self_alarm && empty( $critical_modified ) && empty( $regular_modified ) && empty( $last_results['suspicious'] ) && empty( $last_results['extra'] ) && ! $has_closed ) : ?>
7858 + <?php if ( $last_results && ! $vg_self_alarm && empty( $critical_modified ) && empty( $regular_modified ) && empty( $last_results['suspicious'] ) && empty( $last_results['extra'] ) && ! $has_closed && ! $vg_scan_partial && empty( $vg_missing_core ) ) : ?>
7676 7859 <p class="vigilante-all-clear" style="color: #00a32a; font-weight: bold;">
7677 - <?php esc_html_e( 'Good Job! All files passed integrity check. No issues found.', 'vigilante' ); ?>
7860 + <?php esc_html_e( 'Good job! Nothing to report in the files that were checked.', 'vigilante' ); ?>
7678 7861 </p>
7679 7862 <?php endif; ?>
7680 7863 <?php endif; ?>
7681 7864 </div>
@@ -7724,9 +7907,9 @@
7724 7907
7725 7908 <?php if ( ! empty( $ignored_files ) ) : ?>
7726 7909 <div id="vigilante-section-fi-ignored" class="vigilante-settings-section">
7727 7910 <h2><?php esc_html_e( 'Ignored Files', 'vigilante' ); ?></h2>
7728 - <p class="description"><?php esc_html_e( 'These files are excluded from scan results and email notifications. They will still be scanned but any findings will be hidden.', 'vigilante' ); ?></p>
7911 + <p class="description"><?php esc_html_e( 'These files are excluded from scan results and email notifications. They will still be scanned but any findings will be hidden. To leave a whole folder out, add it to Excluded Paths instead.', 'vigilante' ); ?></p>
7729 7912 <div class="vigilante-paginated-section" data-bulk-mode="unignore">
7730 7913 <div class="vigilante-fi-bulk-bar">
7731 7914 <button type="button" class="button vigilante-bulk-unignore" disabled><?php esc_html_e( 'Stop ignoring selected', 'vigilante' ); ?></button>
7732 7915 <span class="vigilante-fi-bulk-count" aria-live="polite"></span>
@@ -8959,8 +9142,12 @@
8959 9142 $scanned_at = get_option( 'vigilante_last_integrity_scan' );
8960 9143
8961 9144 delete_option( 'vigilante_last_integrity_results' );
8962 9145 delete_option( 'vigilante_last_integrity_scan' );
9146 +
9147 + // Starting over also means being told again: the next scan emails
9148 + // whatever it finds, as if nothing had been reported before.
9149 + delete_option( Vigilante_File_Integrity::NOTIFIED_OPTION );
8963 9150
8964 9151 /*
8965 9152 * A pending change to wp-config.php or the root .htaccess is closed by
8966 9153 * approving it, which takes the network. Clearing the results was one