Integrity Checks

The nightly integrity check looks at the whole site, not just WordPress core. wphoster:run-integrity-checks dispatches one IntegrityCheckJob per ready site; IntegrityCheckService::run() collects findings from five sources into a single IntegrityCheckRun and raises alerts. Every tenant-touching command runs as the site's OS user (sudo -u <os_user>).

SourceWhat runsCatches
corewp core verify-checksums --exclude=…Modified / missing / extra core files
pluginwp plugin verify-checksums --all --format=jsonModified files in wordpress.org plugins. Plugins without published checksums (Divi, ACF Pro, WPML, client plugins) are listed as unverified and covered by the manifest instead.
themewp theme verify-checksums — probed per run; the installed WP-CLI build does not ship it, so summary.theme_checksums = "unavailable" and themes rely on the manifestModified wordpress.org themes (when available)
manifestDiff of disk vs file_manifestsAdded / modified / removed files under plugins, themes, mu-plugins, wp-config.php, .htaccess, .user.ini, root index.php
signatureRegexes from wphoster.integrity.signaturesAny PHP under uploads/cache/upgrade (always critical), webshell patterns in new/changed files (escalates the finding to critical)

Flow

flowchart LR Upd[run-updates 01:00] --> RB[FileManifestService::rebuild] Cmd[run-integrity-checks 06:00] --> Job[IntegrityCheckJob] Job --> Svc[IntegrityCheckService] Svc --> Core[wp core / plugin / theme verify-checksums] Svc --> Scan[FileManifestService::scan] Scan --> Find[find size+mtime] Find --> Hash[sha256sum changed candidates only] Hash --> Sig[WebshellSignatureScanner] Svc --> Run[IntegrityCheckRun + alerts] Run --> UI[Admin / Integrity check runs: accept / quarantine]

File manifest

file_manifests stores, per site and relative path, sha256, size, mtime, recorded_at and a reason (provision, update, agent_install, manual, accepted). It is the panel's definition of "known-good".

Findings and resolutions

IntegrityCheckRun.failed_files is a list of findings: {source, type, file, message, severity, sha256?, size?, mtime?, signatures?, resolution?}. type is one of checksum_mismatch, missing, unexpected, added, modified, removed, php_in_forbidden_dir. summary holds per-source counts, unverified plugins and errors; open_count is the number of findings without a resolution and feeds the dashboard tile, Capacity & insights, and the promote preflight (which blocks while the production site's latest run has open findings).

Alerts

TypeRaised whenSeverity
integrity_checkOpen core/plugin/theme checksum findings, or the run itself erroredintegrity.alert_severity (warning)
file_changeOpen manifest/signature findingsintegrity.file_change_alert_severity (warning); critical if any finding is critical (PHP in uploads/cache/upgrade or a signature match)

Both types are auto-resolved when a later run has no open findings of that kind.

Key files

PathPurpose
app/Services/Integrity/IntegrityCheckService.phpOrchestrates the five sources, builds the run, raises/resolves alerts
app/Services/Integrity/FileManifestService.phpRebuild, scan, accept; update-window handling
app/Services/Integrity/SiteFileReader.phpfind / sha256sum / head / cat / rm / cp as the site user (PHP fallback when the site has no OS user)
app/Services/Integrity/ManifestComparator.phpPure diff: added / removed / candidates → modified / touched
app/Services/Integrity/WebshellSignatureScanner.phpRegex matching over file contents
app/Services/Integrity/QuarantineService.phpQuarantine / restore
app/Console/Commands/RebuildFileManifest.phpwphoster:rebuild-file-manifest
app/Http/Controllers/Admin/IntegrityCheckRunController.phpIndex/show, accept, quarantine, restore
app/Models/IntegrityCheckRun.php, FileManifest.php, QuarantinedFile.phpModels; IntegrityCheckRun::openFindingsTotal() powers the counters

Config (wphoster.integrity)

KeyPurpose
exclude_filesCore paths passed to --exclude (default wp-includes/l10n.php, patched at provisioning)
verify_plugins / verify_themesToggle the WP-CLI checksum steps
manifest_pathsRelative paths covered by the manifest
no_php_paths / php_extensionsDirectories swept for executable files and which extensions count
signaturesid → regex. Invalid regexes make wphoster:rebuild-file-manifest fail and are covered by a unit test.
signature_max_bytes / signature_max_filesRead at most N bytes per file and scan at most N files per run (findings past the cap get signature_skipped)
update_window_grace_minutesPadding around the last UpdateRun when marking changes as expected
alert_severity / file_change_alert_severityBase severities for the two alert types

Scheduling

First deployment: run php artisan wphoster:rebuild-file-manifest --all once before the first scan, otherwise every file is reported as new. See docs/DEPLOYMENT.md.