v1.0.0 to v1.0.0

29 added, 394 removed. Audit B to A.

---
name: wordpress-malware-removal
description: >
- End-to-end WordPress malware removal, security audit, and hardening — catches the stealth
- infections a typical scan overlooks. Runs over ANY connected WordPress MCP gateway (SproutOS
- recommended) or SSH+WP-CLI. Detects and cleans across 38 malware families: web-shells and
- file-managers, auto-login backdoors, database injection, malicious redirects, cron backdoors,
- hidden admins, wp-config/htaccess tampering, webroot shadow-folder infections, and SEO-spam
- cloaking (gambling/pharma). Sees the site the way Google and an attacker do — not just as a
- browser — so it catches payloads that hide from scanners. Whole-server (not just wp-content),
- quarantine-first with live rollback to keep the site up, 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, has a suspected
- web-shell, injected admins, redirect malware, or gambling/pharma spam in Google or link previews —
- or when the user asks to scan/clean/harden a WordPress site, run a security audit, or find how a
- site was breached.
+ Umbrella entry for the WordPress security suite — three agent skills on one gateway-agnostic engine
+ that audit, clean, and harden a WordPress site end to end over any SSH+WP-CLI or WordPress MCP
+ (SproutOS recommended). Use this to route to the right skill: wp-security-audit (read-only "how
+ hackable am I?" A–F posture score), wp-malware-removal (clean a hacked site — cloaking, backdoors,
+ database injection, web-shells — quarantine-first with rollback), and wp-hardening (idempotent,
+ reversible hardening). Run ./install.sh to install all three. Use when a WordPress site is hacked,
+ shows gambling/pharma spam in Google, needs a security audit, or needs hardening.
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:
-
- - **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.*
- - **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.*
-
- 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. (These are four of the **38 malware families** the tool covers — it's an end-to-end
- audit / clean / harden tool, not a single-signature or cloaking-only scanner.)
-
- ---
+ # WordPress Malware Removal — suite router
- ## Three modes — pick by what the user needs
+ This repository ships **three self-contained skills** on one shared, gateway-agnostic engine. This root
+ file is just the router — the real skills live in `skills/` and are installed with `./install.sh`.
- | Mode | When | What it does |
+ | Skill | Use it when | Where |
|---|---|---|
- | **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. **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)
+ | **wp-security-audit** | "How hackable is this site?" — read-only A–F posture score + ranked fixes. Touches nothing. | `skills/wp-security-audit/SKILL.md` |
+ | **wp-malware-removal** | "This site is hacked — clean it." 12-layer forensic scan → verify → quarantine-first removal → prove clean. | `skills/wp-malware-removal/SKILL.md` |
+ | **wp-hardening** | "Lock the doors." Idempotent, reversible hardening — auto-applies safe fixes, asks before risky. | `skills/wp-hardening/SKILL.md` |
- - **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.
+ ## How to use
+ 1. **Install:** `./install.sh` copies all three skills (self-contained: `SKILL.md` + `scripts/` +
+ `references/`) into your agent's skills directory (default `~/.claude/skills`; `--dir` for
+ Codex/Cursor/other; `--skill <name>` for one; `--uninstall` to remove).
+ 2. **Connect a gateway:** SproutOS MCP (recommended), any other WordPress MCP, or plain SSH+WP-CLI. The
+ shared contract is `references/gateway.md`.
+ 3. **Route by need:** unsure? Run **wp-security-audit** first — it tells you whether to Remove, Harden,
+ or relax.
- ## Bundled resources
+ ## Shared safety DNA (all three skills)
+ Read before you act · never irreversible without a restore point · backup is the user's and is enforced,
+ never self-served · quarantine before delete · health-check after every destructive step, auto-rollback
+ on regression · unsure → NEEDS_HUMAN, never auto-delete · honest coverage gaps over silent "all clear."
- - `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).
+ See `README.md` for the full guide, `SPEC.md` for the product/security spec, `ROADMAP.md` for status,
+ and `docs/research-2026-07-wp-security.md` for the sourced research behind the detection/hardening/audit
+ methodology.