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 | includes/class-self-integrity.php +55 -14 3.0.0 → 3.0.3 View file →
@@ -16,10 +16,11 @@
16 16 * write access).
17 17 *
18 18 * What this deliberately does NOT cover: vulnerabilities in Vigilant's own
19 19 * code (this detects tampering, not bugs), and an attacker with database
20 - * write access, who can switch the toggle off or blank the fingerprint, as
21 - * with any security plugin. Both are documented in SECURITY.md.
20 + * write access, who can blank the fingerprint, as with any security plugin.
21 + * There is no toggle to switch off: only the filter of is_on(), and using it
22 + * is reported as critical. Both are documented in SECURITY.md.
22 23 *
23 24 * Entry points:
24 25 * - File Integrity scan (first block of run_scan(), exempt from the time
25 26 * budget and from user exclusions).
@@ -886,9 +887,10 @@
886 887 * - disk != manifest but == wp.org -> manifest_stale (critical)
887 888 * - disk == manifest but != wp.org -> distribution_mismatch (warning)
888 889 * - on disk, not in manifest -> self_extra (critical if PHP or a link)
889 890 * Anchor level:
890 - * - manifest hash != A3, same version -> manifest_replaced (critical)
891 + * - manifest hash != A3, same version -> rebaseline if wp.org confirms the manifest;
892 + * manifest_replaced (critical) otherwise
891 893 * - manifest hash != A3, version changed -> rebaseline if the upgrader did it or wp.org confirms;
892 894 * manifest_replaced if wp.org contradicts it;
893 895 * manifest_unverified (sticky warning) otherwise;
894 896 * a downgrade adds self_downgraded (warning)
@@ -916,8 +918,10 @@
916 918 'wporg' => false,
917 919 'fingerprint' => false,
918 920 ),
919 921 'rebaselined' => false,
922 + // Same version, manifest adopted because wp.org confirms it (3.0.1).
923 + 'manifest_adopted' => false,
920 924 'downgraded' => false,
921 925 );
922 926
923 927 if ( ! $this->is_enabled() ) {
@@ -1014,15 +1018,35 @@
1014 1018 // upgrader hook also fires for a plugin that was not written (a
1015 1019 // bulk update that skipped it, an install that failed before
1016 1020 // copying), so it is not proof that WordPress replaced the files.
1017 1021 if ( $stored_version === $disk_version ) {
1018 - // Same version, different manifest: swapped without an update.
1019 - $findings[] = $this->finding(
1020 - 'manifest_replaced',
1021 - self::MANIFEST_FILE,
1022 - 'critical',
1023 - __( 'MANIFEST.sha256 was replaced without a plugin update. An attacker regenerating the manifest to hide file changes would look exactly like this.', 'vigilante' )
1024 - );
1022 + if ( $wporg_ok && $this->manifest_consistent_with_wporg( $manifest, $wporg ) ) {
1023 + // The manifest on disk IS the one WordPress.org
1024 + // distributes for this version, so whatever was anchored
1025 + // before was not. That happens to a site that had a test
1026 + // zip of a version and then installed the published one:
1027 + // until 3.0.1 it stayed critical for good, because the
1028 + // repair reinstalls those same official files and lands
1029 + // here again (B45 of the roadmap, reproduced on 18 Sep
1030 + // 2026 with a seeded anchor).
1031 + //
1032 + // Adopting it is the same argument the version-change
1033 + // branch below already makes: an attacker who edits
1034 + // files and regenerates the manifest cannot also make
1035 + // WordPress.org's published checksums agree with it,
1036 + // and that agreement is what is being checked here,
1037 + // file by file and in both directions.
1038 + $result['rebaselined'] = true;
1039 + $result['manifest_adopted'] = true;
1040 + } else {
1041 + // Same version, different manifest: swapped without an update.
1042 + $findings[] = $this->finding(
1043 + 'manifest_replaced',
1044 + self::MANIFEST_FILE,
1045 + 'critical',
1046 + __( 'MANIFEST.sha256 was replaced without a plugin update. An attacker regenerating the manifest to hide file changes would look exactly like this.', 'vigilante' )
1047 + );
1048 + }
1025 1049 } else {
1026 1050 // Version changed: the WordPress updater, a manual/FTP
1027 1051 // update, or an attacker who bumped the Version: header to
1028 1052 // dodge the same-version manifest_replaced check above.
@@ -1198,11 +1222,28 @@
1198 1222
1199 1223 // --- Rebaseline A3 ------------------------------------------------
1200 1224 if ( $result['rebaselined'] ) {
1201 1225 $this->capture_fingerprint( 'upgrader' === $context ? 'upgrader' : 'version_change' );
1202 - if ( 'upgrader' !== $context ) {
1226 + if ( 'upgrader' !== $context && $result['manifest_adopted'] ) {
1227 + // Same version, another manifest, and wp.org confirms the one on
1228 + // disk. Saying "version change" here would be false, and a log
1229 + // line that consoles with something untrue is worse than no line.
1203 1230 $this->log(
1204 1231 'self_rebaselined',
1232 + sprintf(
1233 + /* translators: %s: plugin version */
1234 + __( 'Vigilant self-protection: MANIFEST.sha256 of version %s adopted as the reference, confirmed by WordPress.org; baseline refreshed.', 'vigilante' ),
1235 + $disk_version
1236 + ),
1237 + array(
1238 + 'previous_version' => $stored_version,
1239 + 'new_version' => $disk_version,
1240 + ),
1241 + 'info'
1242 + );
1243 + } elseif ( 'upgrader' !== $context ) {
1244 + $this->log(
1245 + 'self_rebaselined',
1205 1246 $result['downgraded']
1206 1247 ? sprintf(
1207 1248 /* translators: %s: plugin version */
1208 1249 __( 'Vigilant self-protection: downgrade to %s detected outside the WordPress updater and confirmed by WordPress.org; baseline refreshed.', 'vigilante' ),
@@ -1954,11 +1995,11 @@
1954 1995 *
1955 1996 * Deliberately NOT gated by the File Integrity instant_alert toggle
1956 1997 * (unlike Vigilante_Plugin_Status): a tamper of the guardian itself is
1957 1998 * the product's maximum alarm. There is no setting that silences it; only
1958 - * the filter of is_on(), which SECURITY.md documents. Findings from a
1959 - * File Integrity scan go in the scan email instead, which follows its
1960 - * notification setting.
1999 + * the filter of is_on(), which SECURITY.md documents. Findings raised by a
2000 + * File Integrity scan come through here too: switching off the report about
2001 + * changed files must not switch off the alarm about the plugin itself.
1961 2002 *
1962 2003 * Sent only from the site that owns the installation's shared files: the
1963 2004 * plugin files are the same for every site and every network of an
1964 2005 * installation, so one email. Deduped by a fingerprint of the finding