Description
Hanu Malware Guard is a self-contained WordPress security plugin covering eight areas:
-
Malware Scanner — recursively scans themes, plugins, mu-plugins and uploads for a curated set of regex heuristics matching common backdoor/webshell patterns (obfuscated eval(), webshell string signatures, command execution of user input, arbitrary file writes, etc). Any executable PHP-family file found inside
wp-content/uploadsis flagged outright, since WordPress never legitimately places runnable code there. -
File Integrity Monitor — hashes every scanned file and records a baseline. Later scans report files that are new, modified, or deleted since the previous pass, so you can see exactly what changed and when.
-
Cron Guard — catches cron-based persistence. WordPress fires whatever hook name is stored in its
cronoption regardless of whether any currently-loaded code registers a handler for it. Malware commonly schedules a job under an unfamiliar hook name and re-adds its own callback dynamically, so the job keeps firing even after the plugin/theme file that “owns” it is deleted. Hanu Malware Guard snapshots every scheduled cron event on each page load, flags any hook with no currently registered callback (“orphan”) or a randomly-generated-looking name, and lets you:- Block a hook: unschedules every pending event for it immediately and blacklists the hook name so WordPress refuses to re-schedule it in the future (via the
pre_schedule_eventfilter). - Trust a hook: marks it as known-good so it stops appearing as a flag.
- Cross-references file changes against flagged cron activity so you can see file changes that happened right around when a suspicious cron job ran.
- Block a hook: unschedules every pending event for it immediately and blacklists the hook name so WordPress refuses to re-schedule it in the future (via the
-
Login Brute-Force Lockout — locks out an IP/username combination after too many failed logins within a configurable window. Runs as an early
authenticatefilter, so it covers both wp-login.php and XML-RPC logins (both authenticate through the same corewp_authenticate()call). Manage active lockouts and view recent failed attempts on the Login Security page. -
Firewall — blocks the handful of request patterns that account for most automated WordPress attacks: XML-RPC pingback amplification abuse (optional full XML-RPC disable),
?author=Nand REST/wp/v2/usersusername enumeration, and requests whose URL contains obvious SQL-injection / path-traversal / PHP-injection strings. Logged-in administrators are always exempt from the query-string check so normal site use is never at risk of self-lockout. Blocked requests are logged on the Firewall page. -
Security Headers — sends
X-Content-Type-Options,X-Frame-Options,Referrer-Policy, and aContent-Security-Policy: frame-ancestors 'self'(clickjacking protection only — no script-src policy, since that reliably breaks themes/page builders unless hand-tuned per site). -
Core File Integrity Check — compares
wp-admin/wp-includesagainst the official checksums WordPress.org publishes for your exact version, and additionally flags any PHP file physically present in those folders that isn’t part of the official manifest at all (a classic place to hide a backdoor, since admins assume “core” never changes). Findings appear in Scan Results alongside everything else. -
Activity Log — records logins (success/failure/lockout), new admin users and role escalations, plugin/theme activation and switching, core/plugin/theme updates, and use of the built-in file editor, so you have a timeline of what changed and by whom.
Additional hardening options (Settings page): block PHP execution inside wp-content/uploads (.htaccess/web.config, with an Nginx snippet shown for reference), and disable the built-in wp-admin file editor.
External services
This plugin makes one outbound HTTP request, only when you run “Check Core File Integrity” (manually or via the daily scheduled scan): it calls the official WordPress.org checksums API at https://api.wordpress.org/core/checksums/1.0/?version={your WP version}&locale={your locale} to fetch the known-good hash list for your exact WordPress version. No site data, file contents, or personal information is sent — only your WordPress version number and locale, which is required for the API to return the right checksum set. See the WordPress.org API documentation and privacy policy: https://wordpress.org/about/privacy/. No other external service is contacted by this plugin.
Screenshots




Installation
- Upload the
hanu-malware-guardfolder to/wp-content/plugins/, or install directly from the WordPress Plugin Directory. - Activate the plugin through the ‘Plugins’ menu in WordPress.
- Go to Malware Guard Dashboard and run your first scan.
- Review Malware Guard Cron Monitor for any orphaned/suspicious jobs.
- Review Malware Guard Settings to tune login lockout thresholds, firewall toggles and security headers for your site.
FAQ
-
Will this definitely find every piece of malware on my site?
-
No security scanner can guarantee that, and you should be skeptical of any that claims to. The signature scanner and core-integrity checker produce heuristic matches for you to review — they are a strong starting point, not a certified clean bill of health. Always keep offline backups regardless.
-
I got a “critical” finding but I’m sure the file is legitimate — what now?
-
Inspect the snippet shown in Scan Results. Several signatures (obfuscated
eval(),create_function(), long base64 blobs) can legitimately appear in caching layers, minified vendor code, or some page builders. If you’re confident it’s a false positive, click “Ignore” rather than deleting the file. Auto-quarantine of critical findings is off by default for exactly this reason. -
Can it stop a malicious cron job from ever writing a file?
-
Not in real time — no WordPress plugin can intercept a file write at the PHP-engine level; that requires a hosting-level security layer. What it does is control cron scheduling (block a hook so WordPress refuses to re-run it) and detect file changes that happened around the same time as a suspicious cron run, so you can act quickly.
-
Does the login lockout affect XML-RPC too?
-
Yes. XML-RPC authentication goes through the same core
wp_authenticate()function as the normal login form, so the same failed-attempt counter and lockout apply to both. -
Will the firewall lock me out of my own site?
-
The query-string pattern check always exempts logged-in administrators, specifically so normal plugin/editor use can never trigger a self-lockout. Only unauthenticated requests are checked against the SQLi/path-traversal/PHP-injection patterns.
-
Does this send any of my data to a third party?
-
No. The only outbound request the plugin makes is to WordPress.org’s own official checksums API, and only your WordPress version + locale are sent (see “External services” above). Everything else — scanning, hashing, cron auditing, login tracking — runs entirely on your own server.
-
What happens to quarantined files if I uninstall the plugin?
-
Nothing is left on disk. Quarantining a file reads its content into the database (base64-encoded, in the plugin’s own scan_results table) and deletes the original — the file is gone from the filesystem the moment it’s quarantined, not just hidden behind a protected folder. Uninstalling the plugin removes that table along with everything else, so if you want to keep a quarantined file’s content for forensics, restore or export it before uninstalling.
-
Where can I get support?
-
Use the plugin’s support forum on WordPress.org, or email herry@hanuitsolutions.com.
Reviews
There are no reviews for this plugin.
Contributors & Developers
“Hanu Malware Guard” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “Hanu Malware Guard” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
1.2.8
- Fixed: the file-editor activity-log hook read $_POST[‘file’]/$_POST[‘theme’] and used them to build a nonce-action string before sanitizing — even though the only sink is wp_verify_nonce() (a hash comparison, not an executable/output context), the fix follows “sanitize early” literally: both values now go through sanitize_text_field() immediately, before any use. This is a no-op for every legitimate request, since real file paths and theme slugs never contain the characters it strips.
- Hardened several other raw $_GET/$_SERVER reads in the firewall (author-enumeration check, REST user-enumeration check, malicious-pattern scanner) with explicit per-line justification for why they’re intentionally inspected unsanitized — each is used only for validation (is_numeric()), substring comparison, or pattern-matching against attack signatures, never stored or output without separate escaping.
1.2.7
- Fixed: WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery — a LIKE clause value was baked directly into the query string instead of passed through a placeholder. The schema-migration column check now passes its LIKE value via %s, and uninstall.php’s options cleanup now runs the pattern through $wpdb->esc_like() (it contains a real % wildcard) before passing it as %s, rather than hardcoding the pattern in the SQL.
1.2.6
- Fixed: dozens of static-analysis findings from the official Plugin Check tool (WordPress.DB.PreparedSQL.InterpolatedNotPrepared / PluginCheck.Security.DirectDB.UnescapedDBParameter) where a table name was interpolated into a SQL string across several files. All custom-table queries now pass the table name through $wpdb->prepare()’s %i identifier placeholder instead — this also closes a few cases where an existing
phpcs:ignorecomment sat on the wrong physical line of a multi-line prepare() call and so wasn’t actually suppressing anything. %i requires WordPress 6.2+, so the minimum supported version is now 6.2 (previously 6.0). - Fixed: the legacy quarantine-folder cleanup used raw unlink()/rmdir() instead of the WP_Filesystem API (Plugin Check ERROR-level finding); it now goes through WP_Filesystem, and silently skips cleanup if direct filesystem access isn’t available rather than failing.
- Fixed: two remaining input-handling findings — added the missing sniff code to an existing justified
phpcs:ignoreon the file-editor’s $_POST[‘file’] read, and added a missingphpcs:ignore(with rationale) to the read-only $_GET check behind the settings-saved admin notice.
1.2.5
- Fixed: quarantine no longer writes anything to the filesystem. Previously a quarantined file was moved into a protected subfolder under wp-content/uploads — the plugin directory guidelines don’t allow storing potentially-executable code there at all, even in a plugin-specific, .htaccess-protected folder, since that protection isn’t guaranteed to be honored (Nginx ignores .htaccess entirely; offloaded/CDN-backed uploads bypass it too). Quarantine now reads the file’s bytes into the database (base64-encoded) and deletes the original from disk; restoring writes it back from the database. Sites upgrading from an earlier version automatically migrate any already-quarantined files into the database and remove the legacy on-disk folder on first load after updating.
1.2.4
- Fixed: the activity-log entry for built-in theme/plugin file-editor usage was written before verifying the nonce, so a CSRF-forged request from a user with edit_themes/edit_plugins capability could produce a false log entry even though core’s own handler would go on to reject the edit. It now verifies the same nonce core checks (edit-plugin_{file} / edit-theme_{file}{stylesheet}) before writing anything, and simply skips logging on failure without interfering with core’s own request handling.
1.2.3
- Fixed: the Quarantine action could move a file out of wp-admin/wp-includes for core-integrity findings, which counts as modifying a WordPress core directory. Findings inside core directories can now only be reviewed or ignored from Scan Results; the UI explains that core files must be restored via Dashboard Updates Re-install Now, or replaced manually.
- Fixed: the activity-log entry for built-in theme/plugin file-editor usage could previously be triggered by any logged-in user, since it hooked core’s ajax action at a priority that ran before core’s own nonce/capability check. It’s now gated by its own capability check (edit_themes/edit_plugins) before logging anything.
1.2.2
- Fixed: is_writable() replaced with wp_is_writable() (Windows-safe core wrapper).
- Fixed: shortened the readme short description to meet the 150-character limit.
- Fixed: every admin view file now defines its own prefixed render function instead of running top-level template code, so its local variables are function-scoped rather than looking like unprefixed globals to static analysis.
- Fixed: corrected the placement of several phpcs:ignore suppressions that were on the wrong line to take effect, and added missing ones for direct-query/no-caching/unescaped-parameter findings against this plugin’s own custom tables (table names always come from the internal WPMG_DB::table() helper, never user input).
- Fixed: clarified/suppressed the DISALLOW_FILE_EDIT prefix warning (it’s a WordPress core-mandated constant name) and the REMOTE_ADDR sanitization warning (already validated via filter_var() immediately after).
- No functional changes — this release addresses static-analysis findings only.
1.2.1
- Renamed the main bootstrap class from the unprefixed WP_Malware_Guard to WPMG_Plugin, per WordPress.org’s naming-collision guidelines.
- Replaced hardcoded wp-content-relative paths with proper WordPress location APIs: WP_PLUGIN_DIR / WPMU_PLUGIN_DIR for plugins/mu-plugins, and theme-root enumeration via wp_get_themes() (so themes registered outside wp-content/themes are scanned too), and fixed the Nginx snippet to derive its path from wp_upload_dir()[‘baseurl’] instead of ABSPATH.
- Moved quarantine storage out of wp-content/wpmg-quarantine into wp-content/uploads/hanu-malware-guard/quarantine, resolved via wp_upload_dir() at runtime (multisite-safe), per the plugin directory’s file-storage-location requirements.
1.2.0
- Renamed from “WP Malware Guard” to “Hanu Malware Guard” (and slug from wp-malware-guard to hanu-malware-guard) to comply with the WordPress.org restriction on plugin names/slugs starting with “wp”.
- Added bulk actions for incident response: “Quarantine All Critical Findings” on Scan Results and “Block All Suspicious Cron Jobs” on Cron Monitor, so a large-scale infection doesn’t require clicking every row individually.
1.1.0
- Added login brute-force lockout (covers wp-login.php and XML-RPC).
- Added a lightweight firewall: XML-RPC pingback lockdown, username-enumeration blocking, malicious query-string blocking.
- Added baseline security response headers.
- Added WordPress core file integrity checking against the official WordPress.org checksums, plus detection of unexpected PHP files in wp-admin/wp-includes.
- Added a site-wide activity/audit log (logins, admin user/role changes, plugin/theme changes, file editor use, firewall blocks).
- Fixed the scanner and quarantine system flagging/moving the plugin’s own files (its signature list legitimately contains strings like “eval” and “FilesMan” as plain text).
1.0.0
- Initial release: scanner, file integrity monitor, cron guard, upload hardening.
