| 1 |
<?php |
| 2 |
/** |
| 3 |
* Guidance texts for self-protection (what a finding means, and how to fix it). |
| 4 |
* |
| 5 |
* Single source used by the File Integrity box, the Security Check detail, the |
| 6 |
* Security Audit details, the admin notices and the alert email, so the five |
| 7 |
* never drift apart. Strings and a mapping only: nothing here reads state or |
| 8 |
* touches the filesystem. |
| 9 |
* |
| 10 |
* Several finding codes share one explanation, so the entry point is a case |
| 11 |
* key (see case_key()), which is also what the File Integrity box groups by: |
| 12 |
* ten modified files are one case with ten paths, not ten explanations. |
| 13 |
* |
| 14 |
* @package Vigilante |
| 15 |
* @since 3.0.0 |
| 16 |
*/ |
| 17 |
|
| 18 |
// Prevent direct access |
| 19 |
if ( ! defined( 'ABSPATH' ) ) { |
| 20 |
exit; |
| 21 |
} |
| 22 |
|
| 23 |
/** |
| 24 |
* Class Vigilante_Self_Integrity_Guidance |
| 25 |
*/ |
| 26 |
class Vigilante_Self_Integrity_Guidance { |
| 27 |
|
| 28 |
/** |
| 29 |
* Case key for a finding. Codes with the same answer share a key. |
| 30 |
* |
| 31 |
* @param string $code Finding code. |
| 32 |
* @param string $severity info|warning|critical. |
| 33 |
* @param string $variant Optional variant set by the finding (unreadable, |
| 34 |
* exec, link, dir, walk). |
| 35 |
* @return string |
| 36 |
*/ |
| 37 |
public static function case_key( $code, $severity = 'critical', $variant = '' ) { |
| 38 |
switch ( (string) $code ) { |
| 39 |
case 'self_modified': |
| 40 |
if ( 'unreadable' === $variant ) { |
| 41 |
return 'unreadable'; |
| 42 |
} |
| 43 |
return ( 'critical' === $severity ) ? 'modified_code' : 'modified_asset'; |
| 44 |
case 'self_missing': |
| 45 |
return 'missing'; |
| 46 |
case 'self_symlink': |
| 47 |
return 'link'; |
| 48 |
case 'self_extra': |
| 49 |
if ( 'link' === $variant ) { |
| 50 |
return 'link'; |
| 51 |
} |
| 52 |
if ( 'dir' === $variant || 'walk' === $variant ) { |
| 53 |
return 'unlistable'; |
| 54 |
} |
| 55 |
return ( 'critical' === $severity ) ? 'extra_exec' : 'extra_file'; |
| 56 |
case 'manifest_replaced': |
| 57 |
case 'manifest_stale': |
| 58 |
return 'manifest_replaced'; |
| 59 |
case 'manifest_missing': |
| 60 |
case 'manifest_invalid': |
| 61 |
return ( 'critical' === $severity ) ? 'manifest_replaced' : 'manifest_soft'; |
| 62 |
case 'manifest_unverified': |
| 63 |
return 'unverified'; |
| 64 |
case 'distribution_mismatch': |
| 65 |
return 'distribution'; |
| 66 |
case 'self_downgraded': |
| 67 |
return 'downgrade'; |
| 68 |
case 'no_anchors': |
| 69 |
return 'no_anchors'; |
| 70 |
case 'cron_cleared_repeatedly': |
| 71 |
return 'cron_repeated'; |
| 72 |
case 'self_disabled': |
| 73 |
return 'disabled'; |
| 74 |
case 'self_hooks_removed': |
| 75 |
return 'hooks_removed'; |
| 76 |
case 'self_stale': |
| 77 |
return 'stale'; |
| 78 |
} |
| 79 |
return 'unknown'; |
| 80 |
} |
| 81 |
|
| 82 |
/** |
| 83 |
* Guidance for one finding. |
| 84 |
* |
| 85 |
* @param array|string $finding Finding array, or a bare code. |
| 86 |
* @return array { key, title, meaning, steps, repair } |
| 87 |
*/ |
| 88 |
public static function for_finding( $finding ) { |
| 89 |
if ( is_array( $finding ) ) { |
| 90 |
$code = isset( $finding['code'] ) ? (string) $finding['code'] : ''; |
| 91 |
$severity = isset( $finding['severity'] ) ? (string) $finding['severity'] : 'critical'; |
| 92 |
$variant = isset( $finding['variant'] ) ? (string) $finding['variant'] : ''; |
| 93 |
} else { |
| 94 |
$code = (string) $finding; |
| 95 |
$severity = 'critical'; |
| 96 |
$variant = ''; |
| 97 |
} |
| 98 |
return self::case_data( self::case_key( $code, $severity, $variant ) ); |
| 99 |
} |
| 100 |
|
| 101 |
/** |
| 102 |
* Guidance for the state itself, when there is nothing wrong to explain |
| 103 |
* (verified, verified with fewer references, turned off, never run). |
| 104 |
* |
| 105 |
* @param array $args { |
| 106 |
* @type string $status Stored status (ok, degraded, warning, critical). |
| 107 |
* @type int $files Files checked. |
| 108 |
* @type array $anchors manifest/wporg/fingerprint booleans. |
| 109 |
* @type bool $enabled Whether the self-check is on. |
| 110 |
* @type bool $has_run Whether it has ever run. |
| 111 |
* } |
| 112 |
* @return array { key, title, meaning, steps, repair } |
| 113 |
*/ |
| 114 |
public static function for_status( $args = array() ) { |
| 115 |
$status = isset( $args['status'] ) ? (string) $args['status'] : ''; |
| 116 |
$files = isset( $args['files'] ) ? (int) $args['files'] : 0; |
| 117 |
$anchors = ( isset( $args['anchors'] ) && is_array( $args['anchors'] ) ) ? $args['anchors'] : array(); |
| 118 |
$enabled = ! isset( $args['enabled'] ) || (bool) $args['enabled']; |
| 119 |
$has_run = ! isset( $args['has_run'] ) || (bool) $args['has_run']; |
| 120 |
|
| 121 |
if ( ! $enabled ) { |
| 122 |
return self::case_data( 'disabled' ); |
| 123 |
} |
| 124 |
if ( ! $has_run ) { |
| 125 |
return self::case_data( 'never_run' ); |
| 126 |
} |
| 127 |
if ( $files < 1 ) { |
| 128 |
return self::case_data( 'no_anchors' ); |
| 129 |
} |
| 130 |
|
| 131 |
$present = count( array_filter( $anchors ) ); |
| 132 |
if ( $present >= 3 ) { |
| 133 |
return self::case_data( 'verified' ); |
| 134 |
} |
| 135 |
if ( empty( $anchors['wporg'] ) ) { |
| 136 |
return self::case_data( 'verified_no_wporg' ); |
| 137 |
} |
| 138 |
return self::case_data( 'verified_partial' ); |
| 139 |
} |
| 140 |
|
| 141 |
/** |
| 142 |
* Explanation for one Security Audit entry about self-protection: which |
| 143 |
* files it was about, what it means and what to do, so the popup of the |
| 144 |
* log says the same as the File Integrity box. |
| 145 |
* |
| 146 |
* @param string $action Event action. |
| 147 |
* @param string|array $extra_data extra_data column (JSON string or array). |
| 148 |
* @param string $severity Severity of the log entry. |
| 149 |
* @return array|null { title, meaning, steps, files } |
| 150 |
*/ |
| 151 |
public static function for_log_event( $action, $extra_data = '', $severity = 'info' ) { |
| 152 |
$cases = array( |
| 153 |
'self_integrity_fail' => '', |
| 154 |
'self_verified_post_update' => 'post_update', |
| 155 |
'self_integrity_restored' => 'restored', |
| 156 |
'self_integrity_scan' => 'checked', |
| 157 |
'self_integrity_captured' => 'captured', |
| 158 |
'self_rebaselined' => 'captured', |
| 159 |
'cron_restored' => 'cron_restored', |
| 160 |
'cron_scheduled' => 'cron_restored', |
| 161 |
); |
| 162 |
$action = (string) $action; |
| 163 |
if ( ! isset( $cases[ $action ] ) ) { |
| 164 |
return null; |
| 165 |
} |
| 166 |
|
| 167 |
$data = is_string( $extra_data ) ? json_decode( $extra_data, true ) : $extra_data; |
| 168 |
$data = is_array( $data ) ? $data : array(); |
| 169 |
|
| 170 |
$files = array(); |
| 171 |
$codes = array(); |
| 172 |
if ( ! empty( $data['findings'] ) && is_array( $data['findings'] ) ) { |
| 173 |
foreach ( $data['findings'] as $entry ) { |
| 174 |
// The log stores each finding as "code:file". |
| 175 |
$parts = explode( ':', (string) $entry, 2 ); |
| 176 |
$code = $parts[0]; |
| 177 |
$file = isset( $parts[1] ) ? $parts[1] : ''; |
| 178 |
$codes[] = $code; |
| 179 |
if ( '' !== $file ) { |
| 180 |
$files[] = $file; |
| 181 |
} |
| 182 |
} |
| 183 |
} |
| 184 |
foreach ( array( 'restored', 'scheduled' ) as $hook_key ) { |
| 185 |
if ( ! empty( $data[ $hook_key ] ) && is_array( $data[ $hook_key ] ) ) { |
| 186 |
foreach ( $data[ $hook_key ] as $hook ) { |
| 187 |
$files[] = (string) $hook; |
| 188 |
} |
| 189 |
} |
| 190 |
} |
| 191 |
|
| 192 |
$key = $cases[ $action ]; |
| 193 |
if ( '' === $key ) { |
| 194 |
$key = $codes ? self::case_key( $codes[0], 'critical' === $severity ? 'critical' : 'warning' ) : 'unknown'; |
| 195 |
} |
| 196 |
if ( 'cron_restored' === $key && 'critical' === $severity ) { |
| 197 |
$key = 'cron_repeated'; |
| 198 |
} |
| 199 |
|
| 200 |
$guidance = self::case_data( $key ); |
| 201 |
$guidance['files'] = array_values( array_unique( $files ) ); |
| 202 |
if ( count( array_unique( $codes ) ) > 1 ) { |
| 203 |
$guidance['meaning'] .= ' ' . __( 'This entry covers more than one kind of finding: File Integrity lists each one with what it means and what to do.', 'vigilante' ); |
| 204 |
} |
| 205 |
return $guidance; |
| 206 |
} |
| 207 |
|
| 208 |
/** |
| 209 |
* What to do when the repair button is not available. |
| 210 |
* |
| 211 |
* @param string $reason general|no_caps|network|renamed. |
| 212 |
* @return array List of steps. |
| 213 |
*/ |
| 214 |
public static function manual_steps( $reason = 'general' ) { |
| 215 |
if ( 'no_caps' === $reason ) { |
| 216 |
return array( |
| 217 |
__( 'Plugin files cannot be changed from WordPress on this site, so Vigilant cannot repair itself here. Ask whoever manages your server to reinstall Vigilant from WordPress.org.', 'vigilante' ), |
| 218 |
); |
| 219 |
} |
| 220 |
if ( 'renamed' === $reason ) { |
| 221 |
return array( |
| 222 |
__( 'This copy of Vigilant lives in a folder with a different name, and the package WordPress.org distributes always installs as vigilante, so repairing from here would leave a clean copy beside the one that is running.', 'vigilante' ), |
| 223 |
__( 'Download Vigilant from WordPress.org and replace the contents of the folder it is installed in, keeping the folder name, or rename the folder back to vigilante and repair from here.', 'vigilante' ), |
| 224 |
); |
| 225 |
} |
| 226 |
if ( 'network' === $reason ) { |
| 227 |
return array( |
| 228 |
__( 'Only your network administrator can repair Vigilant, because its files are shared by every site in the network. Let them know what this page reports.', 'vigilante' ), |
| 229 |
); |
| 230 |
} |
| 231 |
return array( |
| 232 |
__( 'Download Vigilant from WordPress.org and install it from Plugins > Add New Plugin > Upload Plugin, choosing Replace current with uploaded. Your settings, tables and log are kept.', 'vigilante' ), |
| 233 |
__( 'Do not delete Vigilant to reinstall it: deleting a plugin removes its settings, its tables and its log.', 'vigilante' ), |
| 234 |
); |
| 235 |
} |
| 236 |
|
| 237 |
/** |
| 238 |
* The three steps shared by every case that a reinstall fixes. |
| 239 |
* |
| 240 |
* @param bool $tail Whether to add the what-if-it-comes-back step. |
| 241 |
* @return array |
| 242 |
*/ |
| 243 |
private static function repair_steps( $tail = true ) { |
| 244 |
$steps = array( |
| 245 |
__( 'Repair Vigilant: it downloads a clean copy from WordPress.org and replaces only the plugin files, so your settings, tables and log are kept.', 'vigilante' ), |
| 246 |
__( 'Run a new scan from this tab to confirm the finding is gone.', 'vigilante' ), |
| 247 |
); |
| 248 |
if ( $tail ) { |
| 249 |
$steps[] = __( 'If it comes back after repairing, someone can still write to your server: change your hosting, FTP and administrator passwords, review the administrator accounts and ask your host to check the server.', 'vigilante' ); |
| 250 |
} |
| 251 |
return $steps; |
| 252 |
} |
| 253 |
|
| 254 |
/** |
| 255 |
* Title, meaning and steps of each case. |
| 256 |
* |
| 257 |
* @param string $key Case key. |
| 258 |
* @return array { key, title, meaning, steps, repair } |
| 259 |
*/ |
| 260 |
public static function case_data( $key ) { |
| 261 |
$repair = true; |
| 262 |
switch ( $key ) { |
| 263 |
case 'modified_code': |
| 264 |
$title = __( 'Code file modified', 'vigilante' ); |
| 265 |
$meaning = __( 'This Vigilant code file no longer matches the version published on WordPress.org. Something with write access to your server changed it: an attacker, a script, or a manual edit.', 'vigilante' ); |
| 266 |
$steps = self::repair_steps(); |
| 267 |
break; |
| 268 |
case 'missing': |
| 269 |
$title = __( 'File missing', 'vigilante' ); |
| 270 |
$meaning = __( 'A file that is part of Vigilant has been deleted. A missing module stops protecting your site without showing any error.', 'vigilante' ); |
| 271 |
$steps = self::repair_steps(); |
| 272 |
break; |
| 273 |
case 'unreadable': |
| 274 |
$title = __( 'File cannot be read', 'vigilante' ); |
| 275 |
$meaning = __( 'Vigilant cannot read this file, so it cannot check it. Files distributed by WordPress.org are always readable.', 'vigilante' ); |
| 276 |
$steps = array( |
| 277 |
__( 'Repair Vigilant, which restores the file with standard permissions.', 'vigilante' ), |
| 278 |
__( 'If repairing fails, ask your host to fix the permissions of wp-content/plugins/vigilante.', 'vigilante' ), |
| 279 |
); |
| 280 |
break; |
| 281 |
case 'link': |
| 282 |
$title = __( 'Symbolic link inside Vigilant', 'vigilante' ); |
| 283 |
$meaning = __( 'A Vigilant file is a symbolic link, or points outside the plugin folder. WordPress.org never distributes links, so someone put it there.', 'vigilante' ); |
| 284 |
$steps = self::repair_steps(); |
| 285 |
break; |
| 286 |
case 'extra_exec': |
| 287 |
$title = __( 'Executable file added', 'vigilante' ); |
| 288 |
$meaning = __( 'There is an executable file (PHP or similar) in the Vigilant folder that is not part of Vigilant. Hiding files inside a security plugin is a common way to keep a backdoor.', 'vigilante' ); |
| 289 |
$steps = array_merge( |
| 290 |
array( __( 'If you want to know how it got there, ask your host to check when the file was created before you repair, because repairing removes it.', 'vigilante' ) ), |
| 291 |
self::repair_steps() |
| 292 |
); |
| 293 |
break; |
| 294 |
case 'unlistable': |
| 295 |
$title = __( 'Part of the folder cannot be checked', 'vigilante' ); |
| 296 |
$meaning = __( 'Vigilant cannot list one of its folders, or found far more files than it ships, so part of its own folder cannot be checked. A web server can still run files hidden in a folder that cannot be listed.', 'vigilante' ); |
| 297 |
$steps = array( |
| 298 |
__( 'Repair Vigilant, which replaces the folder with a clean copy.', 'vigilante' ), |
| 299 |
__( 'If repairing fails, ask your host to check the permissions of wp-content/plugins/vigilante.', 'vigilante' ), |
| 300 |
); |
| 301 |
break; |
| 302 |
case 'extra_file': |
| 303 |
$title = __( 'Extra file that is not code', 'vigilante' ); |
| 304 |
$meaning = __( 'There is a file in the Vigilant folder that is not part of Vigilant, and it is not executable code. It is often a PHP error log written by your server, or something left behind by a backup or sync tool.', 'vigilante' ); |
| 305 |
$steps = array( |
| 306 |
__( 'If it is an error_log, your server is writing PHP errors inside Vigilant folder: open it, and if the errors mention Vigilant, report them in the Vigilant support forum.', 'vigilante' ), |
| 307 |
__( 'Repairing Vigilant removes the file, because the folder is replaced with a clean copy.', 'vigilante' ), |
| 308 |
); |
| 309 |
break; |
| 310 |
case 'manifest_replaced': |
| 311 |
$title = __( 'Manifest replaced, deleted or damaged', 'vigilante' ); |
| 312 |
$meaning = __( 'MANIFEST.sha256, the list of fingerprints Vigilant uses to check its own files, was replaced, deleted or damaged without a plugin update. Replacing that list is how someone would hide changes to the plugin.', 'vigilante' ); |
| 313 |
$steps = self::repair_steps(); |
| 314 |
break; |
| 315 |
case 'manifest_soft': |
| 316 |
$title = __( 'Manifest missing or damaged', 'vigilante' ); |
| 317 |
$meaning = __( 'MANIFEST.sha256 is missing or is not a valid manifest. Vigilant still checks its files against WordPress.org, but one of its three references is gone.', 'vigilante' ); |
| 318 |
$steps = array( |
| 319 |
__( 'Repair Vigilant to restore the manifest.', 'vigilante' ), |
| 320 |
); |
| 321 |
break; |
| 322 |
case 'distribution': |
| 323 |
$title = __( 'Different from the WordPress.org copy', 'vigilante' ); |
| 324 |
$meaning = __( 'These files match the manifest inside the plugin, but not the copy WordPress.org distributes for this version. On a development copy that is expected; on a live site it means the whole plugin, manifest included, came from somewhere else.', 'vigilante' ); |
| 325 |
$steps = array( |
| 326 |
__( 'If you installed Vigilant from WordPress.org, repair it.', 'vigilante' ), |
| 327 |
__( 'If you are running a development build on purpose, install the published version when you finish testing.', 'vigilante' ), |
| 328 |
); |
| 329 |
break; |
| 330 |
case 'modified_asset': |
| 331 |
$title = __( 'Stylesheet, script or image modified', 'vigilante' ); |
| 332 |
$meaning = __( 'A stylesheet, script or image of Vigilant no longer matches the distributed version. Usually an optimization or cache plugin, or your host, rewrote it while minifying or combining files.', 'vigilante' ); |
| 333 |
$steps = array( |
| 334 |
__( 'If you use an optimization or cache plugin, exclude wp-content/plugins/vigilante from it, then repair Vigilant.', 'vigilante' ), |
| 335 |
__( 'If you do not use one, repair Vigilant and run a new scan.', 'vigilante' ), |
| 336 |
); |
| 337 |
break; |
| 338 |
case 'downgrade': |
| 339 |
$title = __( 'Older version installed', 'vigilante' ); |
| 340 |
$meaning = __( 'Vigilant was replaced by an older version. Older versions can contain vulnerabilities that are already fixed and public.', 'vigilante' ); |
| 341 |
$steps = array( |
| 342 |
__( 'If you did not downgrade it on purpose, repair Vigilant: it installs the version WordPress.org distributes now.', 'vigilante' ), |
| 343 |
); |
| 344 |
break; |
| 345 |
case 'unverified': |
| 346 |
$title = __( 'Version changed, not confirmed yet', 'vigilante' ); |
| 347 |
$meaning = __( 'The Vigilant version changed without the WordPress updater (an FTP upload, a file manager, a deployment), and the new files cannot be confirmed yet because WordPress.org has not published checksums for that version.', 'vigilante' ); |
| 348 |
$steps = array( |
| 349 |
__( 'If you just updated Vigilant by hand with a copy from WordPress.org, you do not need to do anything: the next checks confirm it once WordPress.org publishes the checksums, usually within a day.', 'vigilante' ), |
| 350 |
__( 'If you did not update it, or the copy did not come from WordPress.org, repair Vigilant.', 'vigilante' ), |
| 351 |
); |
| 352 |
break; |
| 353 |
case 'no_anchors': |
| 354 |
$title = __( 'Vigilant could not check its files', 'vigilante' ); |
| 355 |
$meaning = __( 'The manifest is not available and WordPress.org could not be reached, so there was nothing to check the files against.', 'vigilante' ); |
| 356 |
$steps = array( |
| 357 |
__( 'Open Tools > Site Health. If your server cannot connect to WordPress.org, ask your host to allow it.', 'vigilante' ), |
| 358 |
__( 'Then repair Vigilant to restore the manifest.', 'vigilante' ), |
| 359 |
); |
| 360 |
break; |
| 361 |
case 'cron_repeated': |
| 362 |
$title = __( 'Scheduled tasks keep disappearing', 'vigilante' ); |
| 363 |
$meaning = __( 'Vigilant scheduled tasks keep being removed and Vigilant keeps scheduling them again. While they are gone there are no scheduled scans, no daily check and no alerts.', 'vigilante' ); |
| 364 |
$steps = array( |
| 365 |
__( 'Check whether a cleanup or optimization plugin removes scheduled events, and exclude the ones whose name starts with vigilante_. A plugin such as WP Crontrol lists them.', 'vigilante' ), |
| 366 |
__( 'If you cannot find the cause, treat it as a possible compromise: review recently installed plugins and the administrator accounts.', 'vigilante' ), |
| 367 |
); |
| 368 |
$repair = false; |
| 369 |
break; |
| 370 |
case 'cron_restored': |
| 371 |
$title = __( 'Scheduled task restored', 'vigilante' ); |
| 372 |
$meaning = __( 'One of Vigilant scheduled tasks had disappeared and Vigilant scheduled it again. A cron cleanup plugin, a database restore or a site migration can do that.', 'vigilante' ); |
| 373 |
$steps = array( |
| 374 |
__( 'Nothing to do if it does not happen again. A plugin such as WP Crontrol lists the scheduled events of your site.', 'vigilante' ), |
| 375 |
); |
| 376 |
$repair = false; |
| 377 |
break; |
| 378 |
case 'restored': |
| 379 |
$title = __( 'Integrity restored', 'vigilante' ); |
| 380 |
$meaning = __( 'The files that had been reported match what WordPress.org distributes again, so what was reported before is closed.', 'vigilante' ); |
| 381 |
$steps = array(); |
| 382 |
$repair = false; |
| 383 |
break; |
| 384 |
case 'post_update': |
| 385 |
$title = __( 'Checked right after updating', 'vigilante' ); |
| 386 |
$meaning = __( 'WordPress updated Vigilant and Vigilant checked its own files at the end of that same request, which is when a half copied or replaced file would show up.', 'vigilante' ); |
| 387 |
$steps = array(); |
| 388 |
$repair = false; |
| 389 |
break; |
| 390 |
case 'checked': |
| 391 |
$title = __( 'Files checked', 'vigilante' ); |
| 392 |
$meaning = __( 'Vigilant checked its own files against the references available and found nothing to report.', 'vigilante' ); |
| 393 |
$steps = array(); |
| 394 |
$repair = false; |
| 395 |
break; |
| 396 |
case 'captured': |
| 397 |
$title = __( 'Reference saved', 'vigilante' ); |
| 398 |
$meaning = __( 'Vigilant saved the fingerprint of its manifest in your database. That fingerprint is what makes a swapped or deleted manifest detectable later.', 'vigilante' ); |
| 399 |
$steps = array(); |
| 400 |
$repair = false; |
| 401 |
break; |
| 402 |
case 'verified': |
| 403 |
$title = __( 'Verified', 'vigilante' ); |
| 404 |
$meaning = __( 'Vigilant checked its own files against its three references and they all match: the checksums WordPress.org publishes for this version, the manifest shipped inside the plugin, and the fingerprint kept in your database.', 'vigilante' ); |
| 405 |
$steps = array(); |
| 406 |
$repair = false; |
| 407 |
break; |
| 408 |
case 'verified_no_wporg': |
| 409 |
$title = __( 'Verified with fewer references', 'vigilante' ); |
| 410 |
$meaning = __( 'The files match the manifest shipped inside the plugin and the fingerprint kept in your database, but WordPress.org has not published checksums for this version yet, which is normal for a few hours after a release, or your server could not reach it.', 'vigilante' ); |
| 411 |
$steps = array( |
| 412 |
__( 'Nothing to do. If this lasts more than two days, open Tools > Site Health and check that your server can connect to WordPress.org.', 'vigilante' ), |
| 413 |
); |
| 414 |
$repair = false; |
| 415 |
break; |
| 416 |
case 'verified_partial': |
| 417 |
$title = __( 'Verified with fewer references', 'vigilante' ); |
| 418 |
$meaning = __( 'The files were checked, but one of the three references was not available for this check.', 'vigilante' ); |
| 419 |
$steps = array( |
| 420 |
__( 'Nothing to do. The next check uses every reference that is available then.', 'vigilante' ), |
| 421 |
); |
| 422 |
$repair = false; |
| 423 |
break; |
| 424 |
case 'disabled': |
| 425 |
$title = __( 'Self-protection is switched off by code', 'vigilante' ); |
| 426 |
$meaning = __( 'Nothing is checking that Vigilant own files are intact, so a change to them would go unnoticed. There is no setting that does this: something on this site is using the vigilante_self_integrity_enabled filter, and the files listed here are the ones that hook it.', 'vigilante' ); |
| 427 |
$steps = array( |
| 428 |
__( 'If you did not ask for this, treat it as a compromise: a plugin or a snippet that switches off the check of a security plugin is doing the one thing an attacker needs first.', 'vigilante' ), |
| 429 |
__( 'Open the files listed above and remove that filter, or deactivate whatever added it. The check starts again on its own.', 'vigilante' ), |
| 430 |
__( 'If you turned it off on purpose because this site must not contact WordPress.org, nothing else is wrong: the plugin says so on every screen while it lasts.', 'vigilante' ), |
| 431 |
); |
| 432 |
$repair = false; |
| 433 |
break; |
| 434 |
case 'hooks_removed': |
| 435 |
$title = __( 'Something removed the self-protection hooks', 'vigilante' ); |
| 436 |
$meaning = __( 'Code on this site unhooked the checks that run after updates and on each admin page, so Vigilant would keep showing its last result as if it were current. WordPress says nothing when a callback is removed, so the file that did it cannot be named; the plugins loaded in that request are in the entry of the Security Audit.', 'vigilante' ); |
| 437 |
$steps = array( |
| 438 |
__( 'Look at the Security Audit entry for the list of plugins loaded when it happened, and deactivate them one by one until it stops.', 'vigilante' ), |
| 439 |
__( 'If none of them explains it, treat it as a compromise: change your hosting, FTP and administrator passwords and ask your host to check the server.', 'vigilante' ), |
| 440 |
); |
| 441 |
$repair = false; |
| 442 |
break; |
| 443 |
case 'stale': |
| 444 |
$title = __( 'Not checked recently', 'vigilante' ); |
| 445 |
$meaning = __( 'The last check is more than three days old, so what you see is not a current result. It happens on sites nobody opens and with no cron running, and it also happens when something stopped the check.', 'vigilante' ); |
| 446 |
$steps = array( |
| 447 |
__( 'Run a new scan from this tab. If the date does not move, your scheduled tasks are not running: check them with a plugin such as WP Crontrol.', 'vigilante' ), |
| 448 |
); |
| 449 |
$repair = false; |
| 450 |
break; |
| 451 |
case 'never_run': |
| 452 |
$title = __( 'Not checked yet', 'vigilante' ); |
| 453 |
$meaning = __( 'Vigilant has not checked its own files yet. It checks them in every integrity scan, right after every update, and once a day.', 'vigilante' ); |
| 454 |
$steps = array( |
| 455 |
__( 'Run a scan from this tab to check them now.', 'vigilante' ), |
| 456 |
); |
| 457 |
$repair = false; |
| 458 |
break; |
| 459 |
default: |
| 460 |
$key = 'unknown'; |
| 461 |
$title = __( 'Finding in Vigilant own files', 'vigilante' ); |
| 462 |
$meaning = __( 'Vigilant reported something about its own files that this version has no explanation for.', 'vigilante' ); |
| 463 |
$steps = self::repair_steps(); |
| 464 |
break; |
| 465 |
} |
| 466 |
|
| 467 |
return array( |
| 468 |
'key' => $key, |
| 469 |
'title' => $title, |
| 470 |
'meaning' => $meaning, |
| 471 |
'steps' => $steps, |
| 472 |
'repair' => $repair, |
| 473 |
); |
| 474 |
} |
| 475 |
} |
| 476 |
|