Visual Checks
WPHammer's canary system monitors your WordPress sites for unauthorized changes by comparing the current state of each page against a stored baseline. It detects structural changes to the DOM, new third-party scripts, hidden links, and known malicious patterns — the kind of changes that indicate a site has been compromised.
How canary monitoring works
The canary system operates in two phases:
-
Baseline capture — the
SetCanaryBaselineActionfetches the site's HTML over HTTP (30-second timeout, 2 retries) and extracts a structural fingerprint. This becomes the reference point stored as aCanaryBaselinerecord withis_baselineset to true. -
Scheduled checks — the
DispatchCanaryCheckscommand finds all sites withcanary_enabledset to true that have an existing baseline. For each, it dispatches aRunCanaryCheckJobthat fetches the current HTML, extracts a new fingerprint, and compares it against the baseline.
DOM fingerprinting
The CanaryFingerprinter extracts a structural fingerprint from raw HTML using DOM parsing. The fingerprint includes:
- Navigation hash — an MD5 hash of the site's navigation structure
- Footer hash — an MD5 hash of the footer content
- Scripts hash — an MD5 hash of all script elements
- Meta hash — an MD5 hash of meta tags
- Iframe count — the number of iframes on the page
- Hidden link count — links that are visually hidden (a pharma hack indicator)
- HTML lang attribute — the page language (Japanese SEO hack changes this)
- Script domains — external domains loading JavaScript
- Script URLs — full URLs of all script sources
- Meta tags — key meta tag values
When a check runs, the CanaryFingerprinter.compare() method calculates a diff percentage and details by comparing each element of the baseline and snapshot fingerprints.
What triggers an alert
The RunCanaryCheckAction evaluates the comparison and assigns a CanaryStatus:
- Clean — no differences detected
- Changed — differences detected but below 15% and no critical findings
- Alert — differences at or above 15%, or a critical finding detected
- Error — the check could not complete (network failure, timeout)
Critical findings
Certain changes are always flagged as critical regardless of the diff percentage:
- New iframes — iframes that were not present in the baseline
- Hidden links — links using CSS or inline styles to hide from visitors (a signature of pharma hacks)
- HTML lang changes — the page language switching unexpectedly (common in Japanese SEO spam attacks)
- New script domains — external JavaScript loading from domains not seen in the baseline
Malicious pattern detection
Beyond structural comparison, the CanaryFingerprinter.detectMaliciousPatterns() method scans the raw HTML for known attack signatures:
- Base64-encoded eval blocks — obfuscated PHP/JavaScript injection
- Meta refresh redirects — redirects to external domains via meta tags
- Crypto miner scripts — domains associated with browser-based cryptocurrency mining (coinhive.com, jsecoin.com, cryptoloot.pro, and others)
- Suspicious hidden iframes — iframes designed to be invisible, excluding known legitimate embeds from Google Tag Manager, reCAPTCHA, Facebook, and DoubleClick
Each detected pattern is classified by severity: critical, high, or medium.
Screenshots
When a canary check results in an Alert status, the TakeCanaryScreenshotAction captures a full-page screenshot of the site using the ScrapingBee API. This provides visual evidence of what changed.
Screenshots are stored at storage/canary/{site_id}/{datetime}.png and linked to the CanaryBaseline record via screenshot_storage_path.
Taking a screenshot requires a ScrapingBee API key configured in your team settings. Each screenshot costs 5 API credits (compared to 1 credit for an HTML fetch). The screenshot process includes JavaScript execution with a scenario that scrolls the full page and waits for lazy-loaded images to render.
Notifications
When a canary check returns an Alert status, WPHammer sends a CanaryAlertNotification to the team owner. The notification includes details about what changed and a link to review the findings.
Updating the baseline
Baselines should be updated after you make intentional changes to your site — such as updating a theme, adding a plugin, or restructuring navigation. When you set a new baseline via the SetCanaryBaselineJob, the previous baseline is deactivated (is_baseline set to false) and a new one is created from the current site state.
The baseline job tracks activity so you can see who set it and when in the activity log.
Data retention
Non-baseline canary check records are pruned by the PruneCanaryChecks command. By default, checks older than 30 days are deleted along with their associated screenshots. The retention period is configurable via the --days flag. A --dry-run option is available to preview what would be deleted.
Baseline records are never pruned — only the most recent baseline (with is_baseline set to true) is used for comparisons, but historical baselines are preserved.
Related
- Uptime Monitoring — HTTP availability checks
- Security Findings — Security findings from scans
- Server Security — Security scanning overview
- Site Settings — Enabling canary monitoring per site