---
name: wp-security-audit
description: >
  Read-only WordPress security posture audit — "how hackable is this site right now?" Runs over ANY
  connected WordPress MCP gateway (SproutOS recommended) or SSH+WP-CLI and TOUCHES NOTHING. Cross-checks
  installed plugins/themes/core against the free Wordfence Intelligence vulnerability feed (known CVEs,
  abandoned/closed plugins), audits plugin/theme/custom code for SQLi, XSS, CSRF, broken access control,
  file upload/inclusion, SSRF, object injection, and open redirect, and checks HTTP exposure (REST/author
  user enumeration, XML-RPC, public debug.log, exposed .git/.env/backups, directory listing, missing
  security headers, version disclosure). Produces an A–F posture score across access hygiene, known
  vulnerabilities, config/exposure, code risk, and update status, with a ranked, honest fix list and
  explicit coverage gaps. Use when the user asks how secure/hackable a WordPress site is, wants a
  security audit or vulnerability scan, or wants a prioritized hardening plan. If the site is already
  hacked, use wp-malware-removal; to apply fixes, use wp-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 Security Audit (read-only)

You produce a forensic, **read-only** security posture assessment. You touch nothing — no writes, no
deletes, no config changes. Output an **A–F score** with a ranked, honest fix list and explicit coverage
gaps. A good grade means *well-guarded*, **not** *unhackable* — say so.

One of three sibling skills: this **wp-security-audit** (posture), **wp-malware-removal** (clean a hack),
**wp-hardening** (apply fixes). Shared gateway contract in `references/gateway.md`.

**Core rule:** never assert a check passed that you did not actually run. If a gateway can't reach a
dimension (e.g. no filesystem → no code audit), **report the gap**, don't fake a PASS. Honesty about
coverage is the differentiator.

---

## Step 0 — Establish the gateway
Read `references/gateway.md`. Detect access (SproutOS MCP ▸ other WP MCP ▸ SSH+WP-CLI), probe
capabilities, record environment shape (WP root, real `$table_prefix`, multisite, server type, site
URL). Audit degrades gracefully: HTTP-exposure checks need only the public URL; the code audit and
version inventory need file/exec access.

---

## The four audit passes

### 1. Config & exposure (HTTP-reachable; needs only the site URL)
`python3 scripts/hardening_audit.py https://SITE --json hardening.json`. Checks: REST user enumeration
(`/wp-json/wp/v2/users`), author enumeration (`/?author=1`), XML-RPC exposed, public
`wp-content/debug.log`, directory listing, missing security headers (HSTS, X-Content-Type-Options,
X-Frame-Options, Referrer-Policy, CSP), version disclosure (`readme.html`, generator), HTTPS enforcement.

**Also probe these exposure classes (all GET, read-only, touches nothing):**
- **`GET /.git/HEAD`** returning `ref: refs/heads/` → full source (and often secret) disclosure. **High.**
- **`GET /.env`** → DB/API credentials. **Critical.**
- **Backup / DB-dump probes:** `/wp-config.php.bak`, `/wp-config.php.save`, `/.wp-config.php.swp`,
  `/wp-config.txt`, `/backup.zip`, `/database.sql`, `/*.sql.gz`. **Critical** on any hit.
- **Full-path disclosure** (trigger a benign error → absolute server paths) and **open registration +
  weak lockout** (WSTG-ATHN).

The account/file checks that need a gateway (2FA state, admin list, Application Passwords, file perms,
`DISALLOW_FILE_EDIT`, salts age, login lockout) — run those over SproutOS/WP-CLI and record results.

### 2. Vulnerable components (known-CVE cross-check)
`python3 scripts/wordfence_client.py --installed installed.json` — installed versions vs the **free
Wordfence Intelligence v3 feed** (personal + commercial use; free key; ~1 request/30 min → the client
downloads the whole feed once and matches locally, 6h cache). Prioritize RCE / auth-bypass / arbitrary-
file-upload. Setup in `references/wordfence-integration.md`. *(Verify the v2→v3 cutover date against the
live Wordfence doc — see `docs/research-2026-07-wp-security.md`.)*

**Also flag (free, no key):**
- **Abandoned / closed plugins** via `https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]={slug}`
  — a closed plugin returns "Plugin has been closed!" (a **red alert** — WordPress pulled it, often for
  an unpatched vuln, and it will never auto-update); flag `last_updated` older than ~24 months as abandoned.
- **Nulled/pirated plugins** — a premium slug absent from the repo with no legit update channel, a
  divergent version string, or "nulled"/"GPL club" header comments (the backdoor is the real finding —
  hand that to wp-malware-removal).

Secondary confirmation only: **WPScan** (free tier 25 calls/day, non-commercial) for the top ≤10 flagged
components; **Patchstack** public DB as a manual lookup (its API is paid).

### 3. Code audit (SAST over plugin/theme/custom code)
`python3 scripts/code_auditor.py WPROOT --json audit.json` + `scripts/audit_rules.json`. Taint model:
an untrusted **source** (`$_GET/$_POST/$_REQUEST/$_COOKIE/$_SERVER/$_FILES`, shortcode `$atts`, REST/AJAX
body) reaching a dangerous **sink** without an appropriate **sanitizer**. Classes: SQLi, stored/reflected
XSS, CSRF/nonce-misuse, broken access control (`register_rest_route` `permission_callback =>
'__return_true'`; `wp_ajax_` without `current_user_can`), arbitrary file upload, LFI/RFI, SSRF, object
injection (`unserialize` on input), open redirect, dynamic exec. Also flag files **missing
`defined('ABSPATH') || exit;`** (direct-access check).

Two honesty rules: **`sanitize_text_field` is NOT a sufficient sanitizer** for attribute-context XSS or
SQLi (it doesn't escape quotes / ignores output context) — don't treat it as universal. And regex SAST
**over-flags** — every candidate is `heuristic` until read in context. For **third-party plugins,
report + recommend update/replace**; for **custom code, suggest the fix** — never edit someone else's
plugin. When Psalm (taint mode) or Progpilot is installed, prefer their dataflow result and degrade to
regex otherwise.

### 4. Score (A–F, ranked fixes)
`python3 scripts/posture_score.py --hardening hardening.json --code audit.json --vulns vulns.json`.
Five OWASP-aligned dimensions, each 0–100 by penalty subtraction (critical −30 / high −15 / medium −7 /
low −3), then weighted:

| Dimension | Weight | Why |
|---|---|---|
| **Access hygiene** | 0.30 | Stolen/weak admin creds are the #1 real breach vector; OWASP A01 (Broken Access Control) is ranked first. 2FA makes a leaked password inert. |
| **Known vulnerabilities** | 0.25 | OWASP A06; a known-exploited plugin CVE is a here-and-now hole. |
| **Config / exposure** | 0.20 | Real but usually lower-impact than a credential or a KEV. |
| **Code risk** | 0.15 | Heuristic / FP-prone, so weighted lower. |
| **Update status** | 0.10 | Partly a proxy already captured by known-vulns. |

Thresholds **A ≥ 90, B ≥ 80, C ≥ 70, D ≥ 60, F < 60**. **Grade cap:** if any component has **CVSS ≥ 9.0
and is publicly exploited / in CISA KEV / closed-in-repo, cap the overall grade at D** regardless of the
weighted average — a single wormable RCE must not average away against good headers. Map each finding to
its **OWASP WSTG ID + Top-10 category** in the JSON so the report reads as a professional audit.

---

## The report
- **Grade + one-line verdict** ("B — well-guarded, but one closed plugin is a live hole").
- **Ranked fix list:** severity → ease → access-first. Tag each fix `safe` / `risky` / `manual` (the
  exact classes wp-hardening applies) so the handoff is clean.
- **Per-dimension breakdown** with the findings that drove each score.
- **Coverage gaps, stated plainly:** which dimensions you couldn't assess and why.
- **Handoffs:** vulnerable/abandoned components + code fixes → owner/update; hardening items → wp-hardening;
  any suspected backdoor/nulled payload → wp-malware-removal.

**Framework honesty:** there is **no official "CIS WordPress Benchmark"** — CIS targets the underlying
stack (OS, web server, DB, PHP), not the CMS. Cite the WordPress Hardening handbook + CIS OS/PHP
benchmarks; do not claim a CIS WordPress benchmark. Map to OWASP WSTG (INFO/CONF/ATHN/ATHZ) and the
OWASP Top 10 instead.

## Bundled resources
- `references/gateway.md` — reach the site over SproutOS / any WP MCP / SSH+WP-CLI.
- `references/wordfence-integration.md` — free v3 API key + matching installed versions.
- `references/edge-cases.md` — multisite, custom prefixes, coverage-gap discipline.
- `scripts/hardening_audit.py` (config/exposure) · `scripts/code_auditor.py` (+`audit_rules.json`) ·
  `scripts/wordfence_client.py` (vuln feed) · `scripts/posture_score.py` (A–F + ranked fixes).
