PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 3.0.0
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v3.0.0
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 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
← All changes | includes/class-file-integrity.php +116 -2 2.11.123.0.0 View file →
@@ -1771,8 +1771,16 @@
1771 1771 $files = array( $hook_extra['plugin'] );
1772 1772 }
1773 1773 foreach ( $files as $file ) {
1774 1774 $slug = dirname( (string) $file );
1775 + // Vigilant itself is verified immediately (not after 90 s) by
1776 + // Vigilante_Self_Integrity::handle_upgrader() when the
1777 + // self-check is on; do not also open a grace window for it.
1778 + // Compared with the folder it really lives in, not the literal
1779 + // slug, so a renamed folder is skipped the same way.
1780 + if ( dirname( VIGILANTE_PLUGIN_BASENAME ) === $slug && Vigilante_Self_Integrity::is_on() ) {
1781 + continue;
1782 + }
1775 1783 if ( '.' !== $slug && '' !== $slug ) {
1776 1784 $targets['plugin'][] = $slug;
1777 1785 }
1778 1786 }
@@ -1841,8 +1849,17 @@
1841 1849 */
1842 1850 private function verify_updated_slug( $type, $slug ) {
1843 1851 $grace_key = 'vigilante_fi_grace_' . $type . '_' . md5( $slug );
1844 1852
1853 + // Defensive skip for the first 2.11.x -> 3.0.x update: the OLD code in
1854 + // memory scheduled this event including Vigilant's own slug, and by the
1855 + // time it fires the NEW code (this one) is running with the self-check
1856 + // handling Vigilant on its own.
1857 + if ( 'plugin' === $type && dirname( VIGILANTE_PLUGIN_BASENAME ) === $slug && Vigilante_Self_Integrity::is_on() ) {
1858 + delete_transient( $grace_key );
1859 + return;
1860 + }
1861 +
1845 1862 if ( 'plugin' === $type ) {
1846 1863 if ( ! function_exists( 'get_plugins' ) ) {
1847 1864 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1848 1865 }
@@ -1993,8 +2010,41 @@
1993 2010
1994 2011 // Use settings from options page
1995 2012 $options = is_array( $this->options ) ? $this->options : array();
1996 2013
2014 + // Vigilant self-check runs FIRST and exempt from the time budget:
2015 + // ~60 small-file hashes cost < 50 ms and the guardian must never be
2016 + // dropped by the budget on plugin-heavy sites. User exclusions do not
2017 + // apply to it (see Vigilante_Self_Integrity::run_check()).
2018 + if ( ! class_exists( 'Vigilante_Self_Integrity' ) ) {
2019 + require_once VIGILANTE_INCLUDES_DIR . 'class-self-integrity.php';
2020 + }
2021 + if ( Vigilante_Self_Integrity::is_on() ) {
2022 + if ( ! class_exists( 'Vigilante_Self_Integrity' ) ) {
2023 + require_once VIGILANTE_INCLUDES_DIR . 'class-self-integrity.php';
2024 + }
2025 + $self = new Vigilante_Self_Integrity( $this->settings, $this->activity_log );
2026 + $self_result = $self->run_check( 'scan' );
2027 + // run_check() above updates this site's own state (status line and
2028 + // the analyzer check keep working everywhere). The plugin files are
2029 + // shared by the whole installation, so the self findings are folded
2030 + // Self-protection has its own alert, and it does not travel in the
2031 + // scan digest any more. That digest is governed by a notification
2032 + // setting that can be switched off, and switching off "tell me about
2033 + // changed files" was also switching off the alarm of the plugin
2034 + // itself. So the findings stay out of the scan results (they have
2035 + // their own block in File Integrity, with what each one means and how
2036 + // to repair it) and a critical one sends its own email from here,
2037 + // wherever the scan runs. maybe_send_self_alert() keeps it to the
2038 + // site that owns the shared files and dedupes by set of findings.
2039 + foreach ( (array) $self_result['findings'] as $self_finding ) {
2040 + if ( 'critical' === ( $self_finding['severity'] ?? '' ) ) {
2041 + $self->maybe_send_self_alert( $self_result['findings'], 'scan' );
2042 + break;
2043 + }
2044 + }
2045 + }
2046 +
1997 2047 // Scan uploads for suspicious files FIRST (highest security priority)
1998 2048 // PHP files in uploads are almost always malware
1999 2049 if ( ! empty( $options['scan_uploads'] ) && ! $this->is_time_exceeded() ) {
2000 2050 $upload_results = $this->scan_uploads();
@@ -3514,8 +3564,17 @@
3514 3564 if ( '.' === $plugin_slug ) {
3515 3565 continue;
3516 3566 }
3517 3567
3568 + // With the self-check on, Vigilant itself is verified by the
3569 + // sha256 triple-anchor block at the start of run_scan(): scanning
3570 + // it here again would duplicate findings and the md5 fetch. With
3571 + // the check off by filter, Vigilant is a regular plugin (legacy
3572 + // behaviour).
3573 + if ( dirname( VIGILANTE_PLUGIN_BASENAME ) === $plugin_slug && Vigilante_Self_Integrity::is_on() ) {
3574 + continue;
3575 + }
3576 +
3518 3577 // Skip slugs in their post-update grace window: wp.org may still be
3519 3578 // publishing the new version's checksums, so a scheduled scan here
3520 3579 // would raise benign "modified/extra" noise. The dedicated post-update
3521 3580 // verifier (vigilante_fi_postupdate_verify) handles these instead.
@@ -4762,8 +4821,26 @@
4762 4821 if ( is_multisite() && is_array( $item ) && 'critical_config' === ( $item['type'] ?? '' ) ) {
4763 4822 return true;
4764 4823 }
4765 4824
4825 + // Findings about the manifest and the version of Vigilant
4826 + // itself are not about one file, so no entry of the list may
4827 + // hide them, on a single site either: ignoring the row of
4828 + // MANIFEST.sha256 took a replaced manifest out of the email.
4829 + // Vigilante_Self_Integrity::filter_ignored_findings() keeps
4830 + // them the same way.
4831 + if ( is_array( $item ) && 'vigilante_self' === ( $item['type'] ?? '' ) && in_array( $item['self_finding'] ?? '', array( 'manifest_replaced', 'manifest_unverified', 'manifest_missing', 'manifest_invalid', 'self_downgraded' ), true ) ) {
4832 + return true;
4833 + }
4834 + // Nor the findings of the walk of Vigilant's folder (a folder
4835 + // that cannot be listed, the folder that could not be walked):
4836 + // their path ends in a slash, and ignoring that row left the
4837 + // scan with no row and no email while the self-protection
4838 + // status stayed critical.
4839 + if ( is_array( $item ) && 'vigilante_self' === ( $item['type'] ?? '' ) && '/' === substr( (string) ( $item['file'] ?? '' ), -1 ) ) {
4840 + return true;
4841 + }
4842 +
4766 4843 $file = is_array( $item ) && isset( $item['file'] ) ? $item['file'] : '';
4767 4844 return ! in_array( $file, $this->ignored_files, true );
4768 4845 }
4769 4846 )
@@ -4800,9 +4877,17 @@
4800 4877 // a security-critical finding, same tier as a suspicious file.
4801 4878 $closed_plugins = $this->collect_closed_plugins_for_email();
4802 4879 $has_closed = ! empty( $closed_plugins );
4803 4880
4881 + /*
4882 + * Self-protection is not part of this decision any more. Its alert is
4883 + * its own and no setting switches it off, so this email is again about
4884 + * the files of the site: core, plugins, themes, uploads and the two
4885 + * shared configuration files.
4886 + */
4804 4887 $has_suspicious = ! empty( $results['suspicious'] ) || ! empty( $results['extra'] ) || $has_critical_config || $has_closed;
4888 + // Missing files of core, plugins or themes still do not send the email on
4889 + // their own: it has no section to list them in, so it would arrive empty.
4805 4890 $has_modified = ! empty( $results['modified'] );
4806 4891
4807 4892 // Instant alert: send for suspicious, extra, critical_config, modified
4808 4893 // files, or closed plugins.
@@ -4921,8 +5006,37 @@
4921 5006 $regular_modified[] = $item;
4922 5007 }
4923 5008 }
4924 5009
5010 + /*
5011 + * Self-protection does not travel in this email any more: it has its own
5012 + * alert, which no setting switches off (Vigilante_Self_Integrity::
5013 + * maybe_send_self_alert()). Older stored results can still carry its
5014 + * rows, so they are dropped here instead of being listed as ordinary
5015 + * files.
5016 + */
5017 + foreach ( array( 'suspicious', 'extra', 'missing' ) as $self_bucket ) {
5018 + if ( empty( $results[ $self_bucket ] ) || ! is_array( $results[ $self_bucket ] ) ) {
5019 + continue;
5020 + }
5021 + $results[ $self_bucket ] = array_values(
5022 + array_filter(
5023 + $results[ $self_bucket ],
5024 + function ( $item ) {
5025 + return ! ( is_array( $item ) && 'vigilante_self' === ( $item['type'] ?? '' ) );
5026 + }
5027 + )
5028 + );
5029 + }
5030 + $regular_modified = array_values(
5031 + array_filter(
5032 + $regular_modified,
5033 + function ( $item ) {
5034 + return ! ( is_array( $item ) && 'vigilante_self' === ( $item['type'] ?? '' ) );
5035 + }
5036 + )
5037 + );
5038 +
4925 5039 $suspicious_count = count( $results['suspicious'] ?? array() );
4926 5040 $extra_count = count( $results['extra'] ?? array() );
4927 5041 $critical_config_count = count( $critical_config );
4928 5042 $modified_count = count( $regular_modified );
@@ -4927,10 +5041,10 @@
4927 5041 $critical_config_count = count( $critical_config );
4928 5042 $modified_count = count( $regular_modified );
4929 5043 $closed_count = count( $closed_plugins );
4930 5044
4931 - // Use more urgent subject when suspicious files, critical config changes
4932 - // or closed plugins are found (all three are security-critical).
5045 + // Use more urgent subject when suspicious files, critical config changes,
5046 + // closed plugins or self-integrity findings are found (all security-critical).
4933 5047 if ( $suspicious_count > 0 || $critical_config_count > 0 || $closed_count > 0 ) {
4934 5048 $subject = sprintf(
4935 5049 /* translators: %s: Site name */
4936 5050 __( '[%s] SECURITY ALERT: File integrity issues detected', 'vigilante' ),