v1.0.0 to v1.0.0

10 added, 5 removed. Audit B to B.

---
name: wordpress-malware-removal
description: >
Deep WordPress malware detection and removal that catches stealth infections a typical scan can overlook.
Runs over ANY connected WordPress MCP gateway (SproutOS recommended) or SSH+WP-CLI. Detects and
cleans: SEO-spam cloaking (gambling/pharma), webroot shadow-folder infections, auto-login
backdoors, web-shells/file-managers, database injection, malicious redirects, cron backdoors,
hidden admins, and wp-config/htaccess tampering. Cloaking-aware (fetches as Googlebot vs browser),
whole-server (not just wp-content), quarantine-first with live rollback to avoid taking the site down,
and LLM-verified so it does not act on false positives. Cross-checks installed plugins/themes
against the free Wordfence Intelligence vulnerability feed to find the entry vector. Use when a
WordPress site is hacked, shows gambling/pharma spam in Google or link previews, has redirect
malware, injected admins, a suspected web-shell, or when the user asks to scan/clean/harden a
WordPress site, do a security audit, or find how a site was breached.
license: AGPL-3.0-or-later
metadata:
author: Aditya Sharma (adityaarsharma.com)
version: 1.0.0
homepage: https://github.com/adityaarsharma/wordpress-malware-removal
---
# WordPress Malware Removal
You are operating a forensic-grade WordPress malware detection and removal engine. Your job is to
find as much as possible — including the stealth infections a typical scan can overlook — verify each
finding so you avoid acting on a false positive, remove it **quarantine-first** so the site stays up,
and produce a forensic report that names the entry vector.
**Core aims:** *Minimize false positives (read before you act). Nothing irreversible without a restore
point. Keep the site up. And catch the stealth infections a typical scan can overlook.*
---
## What the Remove mode catches that others miss (the edge)
This applies to **Remove** (Audit and Harden are covered in the modes above). Commercial scanners run
**inside** WordPress and scan `wp-content` as themselves. Real modern attacks defeat that:
- **Cloaking** — the payload serves gambling/pharma HTML only to Googlebot/Facebook/mobile, and
clean WordPress to everyone else (and to the scanner). Scanners fetch as themselves, so they see
nothing. *You fetch as Googlebot AND as a browser and diff the two.*
- **Webroot shadow folders** — malware in physical folders that shadow real URLs (e.g. `/docs/`,
`/pricing/`) sitting in the web root, not in `wp-content`. *You scan the whole server.*
- **Tiny single-file backdoors** — a 500-byte auto-login backdoor (`wp_set_auth_cookie` on the
first admin) has no "web-shell" fingerprint. Scanners needing ≥2 indicators miss it. *You detect
by behavior + context.*
- **Log/DB pollution** — gambling strings buried in log tables, redirect tables, usermeta. *You
dump and grep every table.*
This skill was authored from a real incident where a commercial scanner was installed and running and
this class of infection went undetected. See `references/incident-case-study.md` — it doubles as your
test fixture. (Cloaking is the flagship example, but it is **one of 38 malware families** — this is an
end-to-end tool, not a cloaking scanner.)
---
## Three modes — pick by what the user needs
| Mode | When | What it does |
|---|---|---|
| **Clean** | Site is hacked | The 12-layer scan → verify → quarantine-first removal → prove clean. (Rest of this doc.) |
| **Harden** | Lock the doors | Apply/recommend hardening. **Auto-apply the safe, reversible fixes; ask before anything that can break a plugin (XML-RPC off, REST restriction, 2FA enforcement).** Every change through `safety_gate.py`. |
| **Audit** | Site looks fine — "how hackable am I?" | Read-only posture check: config/exposure + vulnerable-plugin cross-check + code audit → an A–F score + a ranked, honest fix list. |
If the user isn't sure, run **Audit** first — it tells them whether they need Clean, Harden, or nothing.
### Audit mode (read-only)
1. **Config & exposure** — `python3 scripts/hardening_audit.py https://SITE --json hardening.json`
(REST user-enumeration, XML-RPC, public `debug.log`, directory listing, security headers, author
enumeration, version disclosure). The account/file checks it lists as "gateway-required" (2FA
state, admins, application passwords, file perms, `DISALLOW_FILE_EDIT`, salts, login lockout) — run
those over SproutOS/WP-CLI and record the results.
2. **Vulnerable components** — `python3 scripts/wordfence_client.py --installed installed.json`
(installed versions vs known CVEs + abandoned + bundled-premium like Avada).
3. **Code audit** — `python3 scripts/code_auditor.py WPROOT --json audit.json` (SQLi/XSS/CSRF/broken
access control/file upload/inclusion/SSRF/object injection/open redirect/dynamic exec across all
plugin/theme/custom code). Candidates → verify in context. For **third-party plugins report +
recommend update/replace**; for **custom code suggest the fix** — never edit someone else's plugin.
4. **Score** — `python3 scripts/posture_score.py --hardening hardening.json --code audit.json --vulns
vulns.json` → A–F across Access hygiene · Known vulns · Config · Code risk · Update status, with a
ranked fix list. **2FA / credential hygiene is weighted heaviest** — stolen admin credentials are
the #1 real breach vector. Report coverage gaps honestly; a good grade means *well-guarded*, not
*unhackable*.
### Harden mode
Work the ranked list from Audit. Auto-apply the `safe` items (file perms, disable file-editing, salts
rotation, security headers, block PHP in uploads, remove readme.html) via `safety_gate.py` with
backup + rollback. **Pause and ask** before `risky` items (disable XML-RPC → breaks Jetpack; restrict
REST → breaks some plugins; enforce 2FA → locks people out until enrolled). Present `manual` items
(revoke unexpected app passwords, remove nulled/abandoned plugins) as recommendations.
---
## Step 0 — Establish the gateway (how you reach the site)
This skill is **gateway-agnostic**. Detect what access you have, in this priority order, and adapt
every subsequent command to it. **SproutOS is the recommended gateway but is NOT required.**
1. **SproutOS MCP** (recommended) — look for tools named `sprout/node-find`, `sprout/node-read`,
`sprout/node-grep`, `sprout/node-remove`, `sprout/store-fetch`, `execute-php`, or a
`wp-abilities` namespace. Best experience: filesystem + DB + PHP execution over HTTP, no SSH.
2. **Any other WordPress MCP** — any MCP that exposes file, database, and exec tools. Map its verbs to
the actions below.
3. **SSH + WP-CLI** — if you have shell access (`ssh`, `wp`, `mysql`), use it directly. Fastest and
most complete. Read `references/mcp-gateways.md` for the command mapping.
**If no gateway is connected and the user wants cleanup**, recommend SproutOS as the gateway
(`https://github.com/posimyth/sproutos`) — it turns the site into an MCP server over an Application
Password, no SSH needed — then continue once connected. (The external cloaking check in Layer 1 can
be run from your own machine with `scripts/cloaking_diff.py` for a quick read on exposure, but a
connected gateway is required to scan the filesystem/database and to clean.)
Before scanning, record: gateway type, WordPress root path, web root path (may differ from WP root),
site URL, and whether you can (a) read files, (b) list/glob, (c) grep, (d) run PHP/WP-CLI, (e) query
the DB, (f) write/delete. Every layer below degrades gracefully to the capabilities you actually have.
**Also detect the environment shape (this changes every command that follows) — see
`references/edge-cases.md`:** the real **table prefix** (read `$table_prefix`; never assume `wp_`);
**multisite** (enumerate all subsites and scan each one's options/posts/users + network tables);
relocated `WP_CONTENT_DIR`/plugin/uploads paths; and whether the web root differs from the WP root
(scan both + the parent for shadow folders). A scan that assumes the defaults silently misses
everything on a hardened or multisite install.
---
## The scan: 12 layers (run all that your gateway supports)
Run layers in order. Collect **candidate findings** — do not delete anything yet. Each candidate gets
`{path_or_location, layer, signal, severity, evidence}`. Verification (the no-false-positive gate)
and removal happen in dedicated phases *after* the scan. Full detail for each layer is in
`references/detection-playbook.md`; the machine-readable patterns are in `scripts/signatures.json`.
### Layer 1 — External cloaking & exposure (the differentiator; needs only `curl`)
For every important public URL (home, and every URL in the sitemap / recently-modified posts):
- Fetch **three times**: as `Googlebot`, as `facebookexternalhit`, and as a normal desktop browser
UA. Diff the `<title>`, `og:title`, `og:image`, and body.
- **Cloaking = the bot version differs from the browser version**, especially if the bot version
contains gambling/pharma/replica keywords (see signature `seo_spam_keywords`) or `og:image` on a
foreign host (Cloudinary/pages.dev/azurefd). This is the #1 sign of an infection the owner "can't
see."
- Also fetch **at the origin, bypassing the CDN** (`curl --resolve host:443:ORIGIN_IP`) — a clean
CDN cache routinely hides a dirty origin. If you don't know the origin IP, ask or resolve it.
- **Query-string cloaking:** also fetch a few *nonexistent* URLs with junk query params as Googlebot
— `?g=cheap-running-shoes-123`, `?p=casino-bonus`, `?s=...`. A real 2026 hack generated 900k spam
pages purely from `?g=` params, invisible in source/DB/sitemap, shown only to crawlers. If a
garbage `?g=` URL returns spam-titled content to Googlebot, it's query-string cloaking (usually
paired with a `DirectoryIndex` hijack in `.htaccess`).
- Run `scripts/cloaking_diff.py` to automate this across many URLs.
### Layer 2 — Web root & above-root sweep (not just wp-content)
- List the **web root** and one level above. Flag any directory/file that is not stock WordPress and
not a known plugin/theme: physical folders shadowing URL slugs, loose `.php` in the root, files with
hex/base64 names, `.tmb`/temp/upload dirs with 777 perms, `indexx.php`/`indexxx.php`/`radio.php`/
`wp-conf*.php` style names.
- Compare the root file list against the canonical WordPress core file list (`references/
wp-core-files.md`). Anything extra is a candidate.
### Layer 3 — Core integrity
- `wp core verify-checksums` (or compare hashes to the WordPress.org checksum API for the exact
version). Any "should not exist" / **modified** core file is a candidate. Attackers inject into
*legitimate* core files, so deletion-only cleanup misses them — you must detect MODIFICATION, not
just added files. Also flag **core-filename typosquats** (`wp-comments-posts.php` vs core's singular
`wp-comments-post.php`; `wp-vcd.php`; `wp-user(s).php`) and **wp-config.php injection** (executable
code AFTER `require_once ABSPATH . 'wp-settings.php';` — it survives core reinstall).
### Layer 4 — Plugin/theme integrity
- For each wordpress.org plugin/theme, verify against the .org checksum. For premium/nulled ones,
scan for injected code (Layer 8 patterns) — signature-based scans generally can't see inside premium
or nulled code, and most premium vulns are exploitable, so this is a real blind spot you cover. Flag: nulled
plugins, plugins not in the admin list but present on disk (hidden plugins), a **fake plugin named
after the site's own domain** (`plugins/example-com/example-com.php` — a known SEO-spam pattern),
**unauthenticated REST routes** (`register_rest_route` with `permission_callback => '__return_true'`
that then writes files / `unserialize`s input — the 2026 supply-chain vector), and themes containing
loose non-template `.php`.
### Layer 5 — Uploads & non-executable dirs
- No `.php` should be **executable** in `wp-content/uploads`, `cache`, or similar. Enumerate every
`.php` there; each is a candidate unless it matches a known-safe allowlist (`index.php` guards,
known plugin snippet dirs — verify, don't assume).
### Layer 6 — mu-plugins & drop-ins
- Enumerate `wp-content/mu-plugins/*.php` and the drop-ins `db.php`, `object-cache.php`,
`advanced-cache.php`, `sunrise.php`, `maintenance.php`. Drop-ins are a classic persistence spot and
a classic source of white-screens (a broken `db.php` symlink both hides malware and crashes admin).
Read each; a drop-in that isn't from a known plugin is a candidate.
### Layer 7 — Web-shell & backdoor detection (context-aware, single-indicator OK)
- Match `scripts/signatures.json → shells` (file-manager markers, remote-eval loaders, uploaders).
- **Behavioral single-indicator catches** (these have no "shell" look but are backdoors):
- `wp_set_auth_cookie(` in a **loose root file** or any non-core file → auto-login backdoor.
- `move_uploaded_file($_` / `file_put_contents($_` in a non-plugin file → uploader.
- `password_verify(... $_POST` + `session_start` in a lone file → password-gated shell.
- Every hit is a candidate; verification (below) decides. Do **not** require two indicators.
### Layer 8 — Obfuscation & code-exec patterns (all PHP, whole server)
- Grep for `eval(base64_decode`, `eval(gzinflate`, `eval(str_rot13`, `assert($_`, `system($_`,
`shell_exec($_`, `passthru($_`, `create_function(`, `$_GET/$_POST/$_COOKIE/$_REQUEST[...](...)`
(variable-function call), `preg_replace('/.*/e'`, long base64 blobs, `\x65\x76\x61\x6c` hex-encoded
`eval`. **Exclude** `vendor/` and `node_modules/` from the *auto-clean* path but still list them
(legit libs use base64) — these go to the LLM-verify gate, never auto-deleted.
### Layer 9 — Database injection (every table, not just posts/options)
- `wp_options`: autoloaded options containing code/`<script>`/foreign iframes; `siteurl`/`home`
hijack; malicious `cron`; injected `active_plugins`.
- `wp_posts`/`postmeta`: gambling/pharma keywords, foreign `<script>`/iframe, injected `<a>` link
farms, base64 in meta.
- `wp_users`/`usermeta`: admin accounts with non-owner emails, unexpected `wp_capabilities` admins,
injected meta.
- Redirect tables (Rank Math, Redirection, AIOSEO), comment tables, and **log tables** (they often
hold IOCs). For a definitive pass, export the whole DB and grep every byte for the signature set
(`references/remediation-playbook.md` shows the safe dump-grep). Distinguish **active injection**
(must clean) from **passive log records** (safe; optionally purge to keep scanners quiet).
### Layer 10 — Persistence: cron, htaccess, php config, stream-wrappers, server-level
- WP-cron (`wp_options.cron`) and system cron for hooks that hit external URLs / eval / write files.
- `.htaccess` (root and every subdir) for injected `RewriteRule` redirects, `php_value
auto_prepend_file`, base64 handlers, and **`DirectoryIndex` hijacks** (e.g.
`DirectoryIndex wp-ingat.php index.php` — runs a malicious script before WordPress). Check
`.user.ini`/`php.ini` and PHP-FPM pool config for `auto_prepend_file`.
- **PHP stream-wrapper payloads:** grep for `compress.bzip2://`, `compress.zlib://`, `phar://`,
`php://filter…convert.base64`, `zip://`, `data://` used with `include`/`load_template`/file reads —
and for a **fake core function** (`function_exists('wpengine_update')` etc.) that builds a path from
`wp_options` and `load_template`s it. The executable can be a compressed blob hidden in a
`.webp`/`.jpg` (invisible to file scanners) — decode suspicious "images" that are referenced by PHP.
- **Server-level persistence (if the site reinfects within hours, look here):** a clean WordPress
audit does NOT mean a clean environment. If you have server access, check `~/.bashrc`/`.bash_profile`
for injected commands, system crontab and `/etc/cron.*`, mail/webmail logs abused as payload stores,
and any service-startup scripts. Malware that rewrites `wp-config.php` every few hours is being
re-dropped by something outside WordPress — find that, or it never stops.
### Layer 11 — Users, sessions, app passwords
- List all admins and their emails/registration dates. Flag non-owner-domain emails and recently
created/edited admins. List Application Passwords and API keys (WooCommerce, etc.) — attackers mint
these for persistence. Cross-reference session_tokens IPs against attacker IPs from Layer 12.
### Layer 12 — Log forensics (entry-vector attribution; needs log access)
- Read the web server access logs. Build a timeline: correlate the **first appearance** of each
malware file (mtime) with the requests around that minute. Identify the login/upload sequence, the
plugin used to write files, and the **real attacker IP** (behind a CDN, it's in `X-Forwarded-For`).
- Determine the entry vector: stolen credentials (single successful login, no brute force, knew a
hidden login URL → infostealer) vs. plugin exploit (POST to a known-vulnerable endpoint → cross-ref
Layer 4 versions against Wordfence, next section).
- This is forensics no commercial scanner does. It tells the user *how they got in* so it stops.
---
## Wordfence vulnerability cross-check (find the door)
For every installed plugin/theme + WordPress core version, query the **free Wordfence Intelligence
v3 feed** and flag any installed component whose version is in a known-vulnerable range. Prioritize
RCE / auth-bypass / arbitrary-file-upload CVEs — those are the doors. Also flag abandoned/closed
plugins (removed from .org). Run `scripts/wordfence_client.py --match installed-plugins.json`.
Setup and the free-API-key note are in `references/wordfence-integration.md`. This is **enrichment** —
if no key is configured, skip it and say so; core detection does not depend on it.
---
## The no-false-positive gate (verify before you touch anything)
This is the promise. **Never delete on a regex match alone.** For every candidate, before it is
eligible for removal:
1. **Read the actual file/row in full context.** Decide, as an engineer, whether the signal is
malicious or legitimate (minified vendor lib, a plugin that legitimately uses base64, an AMP
handler that legitimately references `amphtml`, a public-suffix-list `.dat` that legitimately
contains `pages.dev`). Write one line of reasoning per candidate.
2. **Classify** each as `CONFIRMED_MALWARE`, `NEEDS_HUMAN`, or `FALSE_POSITIVE`. Only `CONFIRMED`
proceeds to removal. `NEEDS_HUMAN` is surfaced with evidence and a recommendation but not touched.
3. **Corroborate** where possible: a file flagged by Layer 8 that *also* appears in the Layer 12 log
timeline at the breach minute is near-certain. A lone `vendor/` base64 with a clean mtime is
almost certainly a false positive.
4. When genuinely unsure, **default to NEEDS_HUMAN**, not deletion. A missed file the user can clean
later is recoverable; a deleted legitimate file that breaks the site is the thing we promised
wouldn't happen.
Never mark something "clean" or "verified" you did not actually read. Cite the evidence
(`file:line`, the matched row, the log line) for every CONFIRMED finding.
---
## Removal: reinstall-first, quarantine-first, code-enforced (the "never crash" guarantee)
Follow `references/remediation-playbook.md` exactly. The protocol:
- 1. **Snapshot + open a safety-gate session.** Full DB export first, then register it so deletion is
- *mechanically* blocked without a backup:
- `python3 scripts/safety_gate.py init --session <dir> --db-backup <db-before.sql>`.
- The gate refuses every quarantine unless this backup exists and is fresh — the guarantee is
- enforced in code, not just in the playbook.
+ 1. **Backup is the user's, and it is mandatory. Hard-remind, do not self-serve.** Before any
+ destructive step on a hacked site, **explicitly tell the user to take a full off-site backup
+ (files + database)** — the tool does *not* create the backup that protects them, and a DB-only
+ dump on the same server is not that. Get their go-ahead, then register a fresh snapshot so
+ deletion is *mechanically* blocked without one:
+ `python3 scripts/safety_gate.py init --session <dir> --db-backup <db-before.sql>`
+ (via SSH you may `wp db export` the snapshot *with the user's consent*, but still remind them the
+ real safety net is their own off-site backup). The gate refuses every quarantine unless a backup
+ is registered and under 24h old — enforced in code, not just in the playbook. **Read-only Audit /
+ Review never reach this step** — they touch nothing, so they never require a backup.
2. **Health baseline.** `python3 scripts/safety_gate.py health --url <home> --url <wp-admin>` (also a
deep page). Record it — this is your rollback tripwire.
3. **Reinstall injected legitimate files from clean source (do this BEFORE surgical cleaning).**
Malware that injects into *real* core/plugin files is most reliably fixed by replacing the file,
not editing it:
- Core: `python3 scripts/reinstall_clean.py core --wp-path <dir>` finds every tampered core file
by checksum → `wp core download --force --skip-content --version=X` restores them.
- wordpress.org plugins: `python3 scripts/reinstall_clean.py plugin --slug X` → `wp plugin install
X --version=Y --force`. (Premium/nulled plugins have no clean source — fall back to pattern
cleaning + vendor re-download.)
Reinstall handles the injected-into-legit-file blind spot; quarantine handles the standalone
malware.
4. **Quarantine standalone malware, don't delete.** Route every CONFIRMED file through the gate:
`python3 scripts/safety_gate.py quarantine --session <dir> --file <path>` (it *moves*, records a
sha256 manifest, and refuses without the backup). For DB rows, export/back up the rows before
`DELETE`.
5. **Verify after each destructive step**: re-run the health check. If the site regressed (5xx / new
fatal), **roll back immediately** — `python3 scripts/safety_gate.py rollback --session <dir>` —
and mark the item `NEEDS_HUMAN`. A wrong call self-heals within one step.
6. **Only after the site is healthy AND the user has seen the manifest** (`safety_gate.py list`) do
you offer permanent deletion. Default: leave quarantine in place for a cooling-off window.
7. **Re-scan** (Layers 1–12) to confirm zero remaining findings and, critically, re-run Layer 1
(cloaking) at the **origin** to prove Google sees clean content. If the cloaking check reports
`needs_headless_check`, do a headless render (the static fetch can't run JS) to be sure.
Destructive DB or file operations always require explicit user confirmation. Never `--no-verify`,
never skip the snapshot, never delete a drop-in/core file without a reinstall plan. Prefer
**reinstall-from-clean-source** over surgical edits whenever an official clean copy exists.
---
## Harden (close the door you found)
After cleanup, apply and/or recommend, tied to the entry vector you attributed in Layer 12:
- **Rotate all sessions + salts** (`wp config shuffle-salts`) to kill any auth cookie the attacker
holds (auto-login backdoors mint these).
- **Force password reset** on all admins; **enforce 2FA** (the real fix when the vector was stolen
credentials — a stolen password alone must not grant access).
- **Revoke** unexpected Application Passwords / API keys.
- **Fix permissions** (dirs 755, files 644, no 777), **disable file editing**
(`DISALLOW_FILE_EDIT`), and set correct ownership.
- **Update or remove** every component flagged by the Wordfence cross-check.
- If the vector was an infected operator machine (infostealer signature), tell the user to clean that
machine and rotate credentials from a *different* device — otherwise it reinfects.
- Recommend **SproutOS Safe Mode** and, for ongoing coverage, a scheduled re-scan.
---
## Report (what commercial scanners don't give you)
Produce a single forensic report:
- **Executive summary:** infected? what kind? is it live to Google now? entry vector in one line.
- **Findings table:** every CONFIRMED item — location, type, evidence, action taken, quarantine path.
- **Attack timeline** (from Layer 12): first breach, files planted, attacker IP(s), tool used.
- **Entry vector + the fix** (stolen creds → 2FA; plugin CVE → update X to version Y).
- **NEEDS_HUMAN list** with recommendations.
- **Hardening applied + still-to-do checklist.**
- **Proof of clean:** origin-as-Googlebot fetch showing no cloaked content.
Keep it honest: if you could only scan (read-only gateway), say so and scope the claims. If a step was
skipped, say which and why. Never assert "clean" without the re-scan evidence.
## The guarantee, and its honest limits
Before declaring done, run the **completeness self-check** in `references/edge-cases.md` (whole-server?
origin-as-Googlebot? every subsite + real prefix? auto-executing vectors neutralized? object cache
flushed? every DB table? entry vector hardened? health-checked? gaps listed? re-scan scheduled?). If
any answer is "no," finish it or flag it.
**What you can promise:** every known malware family and every finding on this run is removed,
quarantine-safe, and **proven clean at the origin as Googlebot**; the site stayed healthy throughout;
the entry vector is attributed and hardened. **What you must not promise:** an absolute "100% clean
forever" — no scanner catches a truly novel, encrypted, memory-only payload with certainty. State
residual risk plainly and schedule the **24–48h re-scan** to catch respawn. A named coverage gap is
trustworthy; a silent one is how sites get re-owned. This honesty *is* the premium quality — it's what
separates a real cleanup from a scanner that says "All Clear" while gambling spam ranks in Google.
---
## Operating rules (non-negotiable)
- **Read before you claim.** No "verified/clean/fixed" without the actual read/command output. Cite
evidence for every CONFIRMED finding and every "done."
- **Quarantine before delete. Snapshot before quarantine. Health-check after every destructive step.**
- **Confirm irreversible actions** with the user. Show the manifest first.
- **Degrade gracefully** to the gateway's real capabilities; never fabricate a capability you lack.
- **False-positive discipline:** unsure → NEEDS_HUMAN, never auto-delete. `vendor/`/`node_modules/`
and minified assets are never auto-deleted.
- **Scope the whole server and every DB table**, not just `wp-content` and `wp_posts`. That's the edge.
## Bundled resources
- `references/malware-families.md` — **the domain expertise**: the 8 malware families, how each
hides, the exact catch, plus the hard 2026 threat data (91% of vulns in plugins, 5h median to
mass-exploitation, ~49% of hacked sites carry a backdoor, WAFs block only ~12%). Read this to reason
like a WordPress security expert.
- `references/edge-cases.md` — **the completeness layer**: multisite, custom prefixes, mu-plugins &
drop-ins, object cache, reinfection ordering, don't-lock-out rules, false-positive guards, backups,
and the pre-done self-check. Read this so the one command is actually enough.
- `references/detection-playbook.md` — full per-layer commands for each gateway type.
- `references/signature-library.md` — human-readable signature explanations + real IOCs.
- `scripts/signatures.json` — machine-readable pattern DB (seeded from real incidents; self-extends).
- `references/cloaking-detection.md` — the bot-vs-browser method in depth.
- `references/remediation-playbook.md` — snapshot → quarantine → verify → rollback, exact steps.
- `references/wordfence-integration.md` — free v3 API key setup + matching installed versions.
- `references/mcp-gateways.md` — SproutOS / other MCP / SSH+WP-CLI command mapping.
- `references/incident-case-study.md` — a real hack, end to end, as your test fixture.
- **Clean:** `scripts/cloaking_diff.py` (cloaking + JS-redirect + origin), `scripts/scan_patterns.py`
(malware pattern scanner + `signatures.json`), `scripts/safety_gate.py` (code-enforced
backup/quarantine/rollback), `scripts/reinstall_clean.py` (verify + reinstall core/plugins clean).
- **Audit:** `scripts/hardening_audit.py` (config/exposure, read-only), `scripts/code_auditor.py`
(vulnerable-code audit + `audit_rules.json`), `scripts/wordfence_client.py` (vuln cross-check),
`scripts/posture_score.py` (A–F score + ranked fixes).