Skip to content

Add wp-ops malware-scan for remote WordPress malware scanning - #228

Merged
jasperf merged 3 commits into
mainfrom
add/remote-malware-scan-command
Sep 11, 2026
Merged

Add wp-ops malware-scan for remote WordPress malware scanning#228
jasperf merged 3 commits into
mainfrom
add/remote-malware-scan-command

Conversation

@jasperf

@jasperf jasperf commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds wp-ops malware-scan — a single command that runs the WordPress malware scanners against a remote site, with checksum verification, from your own machine.

The scanners were reachable by hand only for a site whose files are already local. Scanning a remote one meant scp-ing the scanner to /tmp, running it over SSH, then remembering to delete it. Since 5.22.0 that no longer works at all: both scanners require_once the sibling checksum-verify.php, so a lone copied file fatals on the missing module. The remote-with-checksums capability existed only inside the security_scan MCP tool.

wp-cli/security/malware-scan.sh ports that logic to the CLI. It inlines the checksum module in place of the require_once and streams the assembled, self-contained source to php /dev/stdin on the target, so nothing is written to the server and nothing needs cleaning up. /dev/stdin rather than the conventional -, because Ubuntu/Debian's packaged PHP CLI treats - as a literal filename (#222).

Usage

wp-ops malware-scan example.com production
wp-ops malware-scan example.com production --mode both

SSH host and WordPress path default from the MCP server's site registry (mcp-server/config/sites.json, or $WP_OPS_SITES_CONFIG) when one exists — so a site whose SSH host is not its own domain needs no flags — and fall back to the stock Trellis layout (web@<site-name>, /srv/www/<site>/current/web/wp) when it does not. The registry is gitignored and never embedded in release binaries, so the lookup is entirely optional and degrades silently.

Only those defaults assume Trellis. The scan itself is plain SSH plus a PHP CLI:

wp-ops malware-scan example.com production \
  --host deploy@example.com --path /home/deploy/public_html

environment = development is the one Trellis-only path, running inside the project's Trellis VM via trellis vm shell.

Naming

security-scan was taken by trellis/monitoring/security-scan (Nginx log analysis), and a second entry under that basename would have made both ambiguous to the bare-name dispatcher. malware-scan keeps both resolvable and describes what it actually does.

Changes

  • wp-cli/security/malware-scan.sh — new command
  • wp-cli/security/README.md — both remote-scan sections rewritten; they still described the scp scanner-targeted.php workflow that has been broken since v2.0 of the scanners. The copy-by-hand route survives as a note that both files must travel together.
  • go/internal/catalog/catalog.json — regenerated (go generate ./...)
  • go/internal/catalog/catalog_test.go — entry count 79 → 80
  • CHANGELOG.md — 5.23.0

Verification

  • bash -n under both bash 5.3 and macOS's bash 3.2; shellcheck clean apart from two informational notes (the EXIT trap function, and the deliberate client-side printf %q quoting)
  • Assembled source for both scanners passes php -l, with zero remaining references to checksum-verify.php
  • Streamed the assembled scanner to php /dev/stdin against a local WordPress: checksum verification ran, 3,338 files verified
  • Ran the command end-to-end against a real production site through wp-ops dispatch: resolved host and path from the registry, 1,955 checksum-verified files skipped, 0 checksum failures, no threats
  • go test ./... passes
  • Confirmed wp-ops malware-scan resolves by bare name and wp-ops security-scan still resolves to the existing playbook

Note

The scanners close with a "DELETE THIS FILE after use" warning that does not apply to this path — nothing is copied to the server. The README now says so. Changing the scanners' own output was left out of scope since it affects every other invocation route.

The scanners were reachable by hand only for a site whose files are
already on this machine. Scanning a remote one meant scp-ing the scanner
to /tmp, running it over SSH, then remembering to delete it -- and since
5.22.0 that does not work at all: both scanners require_once the sibling
checksum-verify.php, so a lone copied file fatals on the missing module.
The remote-with-checksums path existed only inside the security_scan MCP
tool.

malware-scan.sh ports that logic to the CLI. It inlines the checksum
module in place of the require_once and streams the assembled,
self-contained source to php /dev/stdin on the target, so nothing is
written to the server and nothing needs cleaning up. /dev/stdin rather
than the conventional - because Ubuntu/Debian's packaged PHP CLI treats
- as a literal filename (issue #222).

Host and path default from the MCP server's site registry when one
exists, so a site whose SSH host is not its own domain needs no flags,
and fall back to the stock Trellis layout otherwise. Only those defaults
assume Trellis: --host and --path aim it at any SSH-reachable WordPress.
The documented remote workflow was to scp scanner-targeted.php to the
server and run it there. That has been broken since v2.0 of the
scanners: they require_once the sibling checksum-verify.php, so a lone
copied file fatals on the missing module.

Both remote sections now point at wp-ops malware-scan. The copy-by-hand
route survives only as a note that both files have to travel together.
@jasperf
jasperf merged commit c73cad0 into main Sep 11, 2026
1 check passed
@jasperf
jasperf deleted the add/remote-malware-scan-command branch September 11, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant