=== Xaflo WP Connect — AI control for WP ===
Contributors: xaflo
Tags: rest-api, automation, mcp, ai, json
Requires at least: 6.0
Tested up to: 7.0
Requires PHP: 8.1
Stable tag: 1.7.0
License: MIT
License URI: https://opensource.org/licenses/MIT

Expose your WordPress site as 90 typed REST endpoints so MCP-compatible clients can read, write and audit it through scoped JWT tokens.

== Description ==

**Xaflo WP Connect** is a lightweight REST API layer for WordPress. It turns the platform into a programmable surface that external tools — including modern AI assistants that speak the Model Context Protocol (MCP) — can drive safely through scoped, short-lived tokens.

I built this plugin because the existing options were poor: application passwords give blanket access, the WordPress REST API is verbose and partial, and writing a custom integration for every site you manage doesn't scale. **Xaflo WP Connect** ships a single, audited surface that any developer or automation tool can target. No vendor lock-in, no SaaS, no account on a third-party server.

= What you get =

* **50 typed REST endpoints** under `/wp-json/wpcb/v1/*` — posts, pages, media, menus, widgets, customizer, taxonomies, SEO, analytics, multilingual, debug log, backup, health checks. Read-only inspection of installed plugins and theme files.
* **JWT scoped authentication** — 23 granular scopes (`content:read`, `media:write`, `cli:exec`, …). Issue a read-only analytics token, a content-only editor token, a full-admin token. Revoke any of them instantly.
* **Capability + scope double check** on every dangerous route: even a JWT with the right scope still has to belong to a user with the matching WordPress capability (`manage_options`, `edit_plugins`, `edit_themes`, `install_plugins`).
* **Confirmation tokens** on irreversible operations — database writes, plugin install from zip, theme switch — so a single API call cannot do silent damage.
* **Automatic backups** before destructive writes. One-call restore via `wp_backup_restore`.
* **Audit log** for every API call: actor, scope, payload, timestamp.
* **Multilingual aware** — works with WPML and Polylang as first-class objects.
* **Maintenance mode toggle** — branded 503 page + IP allow-list bypass.
* **Debug log tail** — read and filter `wp-content/debug.log` over REST.
* **Filesystem browser** — list / read / write files sandboxed to ABSPATH with extension allowlist, path-traversal hardening (`realpath` containment, null-byte stripping), and a hard block on `wp-config.php` writes.
* **JWT secret never in the database** — defined in `wp-config.php` only.

= Why it exists =

Modern AI coding assistants that speak MCP need a structured, typed interface to do useful WordPress work without scraping admin HTML or executing brittle shell scripts. The MCP spec gives them a way to describe tools. This plugin gives WordPress a matching surface — 78 tools with JSON schemas, scoped auth, automatic rollback. The same surface is equally usable from a curl script, a Python automation, a CI job, or your own Node service. There is nothing AI-specific in the wire protocol; it's plain HTTPS + JWT.

= Pricing =

**The plugin is free forever, MIT licensed.** All 78 REST endpoints are available in the free version, with no upsell screens inside WordPress.

If you want a GUI to manage multiple WordPress sites from a single window, I also distribute an OPTIONAL desktop companion app — [Xaflo Desktop](https://wpconnect.xaflo.com/desktop-app/):

* **Xaflo Desktop Lite (free)** — manage **1 connected WordPress site**. All 78 tools. Auto-updates. No card, no signup.
* **Xaflo Desktop Pro (€199/year)** — manage **unlimited sites** + bulk parallel operations (e.g. push a plugin update to 50 sites in one click) + cross-site reports + priority support.

If you manage one WordPress site, you never need Pro — the free desktop already gives you everything. **The plugin itself is identical in both cases; the desktop app is what charges for multi-site GUI convenience.**

= Privacy =

* **No phone-home.** The plugin makes zero outbound HTTP requests of its own. No telemetry, no analytics, no cloud account required.
* **No third-party services contacted.** Every request is between your WordPress install and the client calling the REST API.
* **JWT verification is local** — HMAC-SHA-256 against a secret defined in `wp-config.php`.

= External services (disclosure) =

If you install the plugin from this WordPress.org directory, **no external service is contacted**. The plugin updates through wp.org like any other.

If you install the variant distributed at [wpconnect.xaflo.com](https://wpconnect.xaflo.com/wordpress-plugin/), that variant ships an optional self-hosted update channel pointing at `wpconnect.xaflo.com`. That variant is a separate download and is documented [here](https://wpconnect.xaflo.com/wordpress-plugin/).

= Trademark notice =

"Claude", "Claude Code" and "Claude Desktop" are trademarks of Anthropic PBC. "MCP" / "Model Context Protocol" is an open specification published by Anthropic. **Xaflo WP Connect is an independent open-source project, not affiliated with, endorsed by, or sponsored by Anthropic.** References in this plugin documentation describe compatible client software, not a partnership.

"WordPress" is a registered trademark of the WordPress Foundation.

= Source code =

* **License**: MIT — fork, audit, modify freely.
* **Author website**: [xaflo.com](https://xaflo.com/)
* **Support**: [wpconnect.xaflo.com/contact/](https://wpconnect.xaflo.com/contact/)

== Installation ==

1. Upload via Plugins → Add New → Upload Plugin, or copy the folder to `/wp-content/plugins/`.
2. Activate "Xaflo WP Connect" through the Plugins menu.
3. Add the JWT signing secret to `wp-config.php`:

   `define( 'WPCB_JWT_SECRET', 'paste a 64-char random string here' );`

   The admin page suggests a freshly-generated 64-char string on first load — copy it into `wp-config.php`, then reload.
4. Go to **Xaflo → Tokens → New**, pick the scopes you want to grant, copy the token. The token is shown only once; store it in your password manager.
5. Paste the token + your site URL into the tool you're integrating — any MCP-compatible client, a curl script, a Python automation, your own Node service.

== Frequently Asked Questions ==

= Do I need an account on xaflo.com to use the plugin? =

No. The plugin works fully standalone. The xaflo.com website is the optional companion that distributes the desktop GUI app; the plugin itself never contacts it.

= Is any data sent to a third-party server? =

No. Authentication is HMAC-signed JWT verified locally against a secret in your `wp-config.php`. The plugin emits zero outbound HTTP requests — only inbound requests from the tools you choose to grant tokens to.

= Can I restrict what an integration can do? =

Yes. JWT tokens are scoped — pick exactly which of the 23 granular scopes each token gets. A token with `content:read + analytics:read` cannot write a single character or install a plugin. Revoke any token instantly from the admin page.

= What if a write breaks the site? =

Every destructive write triggers a snapshot of the affected resource (theme file, plugin file, post content). Call `wp_backup_restore` with the snapshot ID to roll back. Snapshots are stored in `wp-content/uploads/xaflo-backups/`.

= Will the plugin work alongside Yoast / Rank Math / SEOPress? =

Yes. The SEO endpoints write to the same standard post-meta keys those plugins use, without owning the UI. Your existing SEO plugin keeps working.

= Does it work with WPML / Polylang? =

Yes — multilingual is first-class. Translations, language-linking and string-translation audits work with both WPML and Polylang.

= Does it work on WP Multisite? =

Yes. Each site in the network gets its own JWT secret and its own tokens. Network-wide bulk operations from a single token are on the roadmap.

= Is `wp_cli_exec` safe? =

It is **disabled by default**. When the site operator opts in (`update_option('wpcb_cli_enabled', true)`), it has a hard allowlist of 16 read-mostly commands (`wp option get`, `wp cache flush`, etc.), rejects shell metacharacters, and uses `escapeshellarg`. The endpoint additionally requires the `cli:exec` JWT scope and `manage_options` on the user the token is bound to.

= Where can I report a security issue? =

Privately at `security@xaflo.com`. Public disclosure 90 days after a fix is shipped.

== Screenshots ==

1. Token management dashboard — issue, list, revoke tokens with scope pickers.
2. Audit log — every API call recorded with actor, scope, payload, timestamp.
3. Active tokens — see when each was last used, from which IP, with which scopes.
4. Scope picker — 23 granular permissions.

== Changelog ==

= 1.7.0 — 2026-11-11 =
Security release with breaking changes. Update as soon as you can, after trying it on a staging copy of your site. It also brings redesigned admin screens, plugin install by upload, quick sign-in to wp-admin from Xaflo Desktop, and an updated connector for Claude (84 tools, and it always asks you before the site makes a change that needs confirming).

**Breaking changes — read before updating:**

* **Rotate and reissue (required):** after updating, go to Xaflo → Settings and click Rotate next to the signing secret, then create new tokens under Xaflo → Tokens and put them in your AI clients and apps in place of the old ones. Earlier versions let a read-only admin token read wp-config.php, where the signing secret lives, and with the secret anyone can make a token with full access. Rotating makes every old token stop working.
* **What site:write means now:** with site:write the file tools can only create, change, move or delete files inside the uploads folder, and only plain files (text, Markdown, CSV, JSON, PDF, images, audio, video, zip and office documents). A file name may have only one extension; extra parts may only be numbers or versions (so "report.v2.pdf" and "data.2024.csv" are fine, "page.html.md" and "archive.tar.zip" are refused), and names cannot contain ":".
* **New fs:admin permission for full file control:** to change other files, including theme and plugin code, .htaccess and files in the site root, issue a token that also has fs:admin. It can change any file on the site and is equivalent to an administrator; every overwrite, rename and delete it makes must be confirmed. Existing tokens do not get fs:admin: if you edit site files through Xaflo Desktop or an AI client, include it when you reissue tokens.
* **Database writes need InnoDB tables:** raw database writes run in a transaction so protected changes can be undone; on sites whose user, option or plugin tables are not InnoDB they are refused. Raw database queries are also refused on servers whose live connection uses NO_BACKSLASH_ESCAPES or ANSI_QUOTES, or a gbk, big5, sjis, cp932 or gb18030 character set, or when those settings cannot be read.
* **Database parameters:** when values are passed separately, only %s, %d and %f placeholders are accepted; put % characters (for example LIKE patterns) in the values.
* **IP allowlist:** it is now enforced, but only after you save it again in Xaflo → Settings. Check first that it lists the addresses your AI clients connect from; a notice reminds you until you do.

**Security fixes:**

* The file tools can no longer read, list, search or download wp-config.php or copies of it, .env files, .bak copies, SQL dumps, debug.log or the plugin's backup folder, however the path is spelled.
* With site:write, file writes never go through a symbolic link, and deleting a folder removes links inside it without following them. If your uploads folder is itself a link to a place outside the WordPress folder, site:write cannot change files at all.
* Raw database queries need an administrator account. Columns holding passwords or activation keys, and values that look like a WordPress or PHP password hash, are removed from every result. A query can still return a hash in a form it encoded itself (for example as hex), which is why raw database access is administrator-only. A database write that changes user accounts, roles or capabilities, application passwords, the plugin's tokens or revocations, or the role, plugin-activation, site-address, admin-email, uploads-location or plugin settings is undone. Many unusual SQL forms and characters are refused; the check is a safety net, not a guarantee.
* When WP-CLI is enabled, commands must match the allowed list exactly, only the options each command needs are accepted, "db check" is no longer allowed by default, and WP-CLI runs in a private folder with empty configuration files.
* Tokens stop working when their user loses the matching WordPress role, every read checks the user's permissions, media reads need media:read, and the automatic SEO fix needs seo:write and only touches posts the user may edit.
* The plugin can no longer be edited, replaced, deleted or deactivated through its own API. Restoring a backup no longer brings back an older copy of this plugin, its token revocation list or its settings; it does restore user accounts and roles as they were in the backup, so reissue tokens after a restore.
* The audit log records each API request once, including refused ones with the token that made them; passwords and keys, file contents, query values, SQL literals, database error text and the secret parts of URLs are not stored. The installer scripts and the maintenance preview are not logged yet. The public health check no longer adds a log row on each call.
* Logged-in admin requests are rate-limited, the broken-link check no longer reaches private network addresses and verifies certificates, HTTPS enforcement can no longer be skipped by a request claiming to come from localhost, and behind a proxy the client address can no longer be faked through X-Forwarded-For.
* Updates are installed only when the download is https from xaflo.com and matches its published checksum; if the update server is unreachable, "Check for updates" now says so instead of "you are on the latest version".

**Added (new features included in this security release):**

* Plugins can be installed by sending the zip file itself to the API (zip_base64, filename and its sha256) instead of a download link. The archive is checked first: the checksum must match, it may be at most 20 MB (or your server's upload limit, if lower), and it must contain exactly one plugin folder with a valid plugin header and no paths outside it. Replacing a plugin that is already installed must be confirmed; this plugin cannot be replaced this way. Nothing is activated unless asked. The audit log records the plugin, version, size and checksum, not the file.
* Quick login for apps such as Xaflo Desktop: a token with the new admin:login permission can ask for a one-time link that opens wp-admin already signed in, as the token's own user. This is equivalent to administrator access and is off unless you issue a token with admin:login (it is never pre-selected, and the Tokens screen warns you). A link works once, for 60 seconds, only over HTTPS, and only for a user who can still manage the site; it stops working if the token is revoked or the signing secret is rotated, and it respects the IP allowlist. Links are refused while a two-factor login plugin (such as Two Factor, WP 2FA or Wordfence) is active, because they would skip the second step. Both the link request and its use are recorded in the audit log, without the link itself.

**Changed:**

* The admin screens have a new design in the Xaflo style, inside normal WordPress admin pages, and can be translated (complete Italian translation included).
* The Tokens screen groups permissions and warns about every powerful one, including fs:admin and admin:login.
* Settings shows whether your IP allowlist is enforced or only saved.
* The reminder about the signing secret now appears only on Xaflo screens and the Plugins screen, where you can dismiss it.
* The maintenance page visitors see has a new design (orange by default).

**Please note:**

* fs:admin, db:write, cli:exec, system:backup, plugin:write and theme:write are powerful permissions. A token with db:write can still change most of the database; cli:exec can read every option; system:backup restores user accounts as they were; fs:admin can change any file except the protected files listed above (wp-config.php and its copies, .env files, .bak copies, SQL dumps, debug.log, .user.ini, php.ini, web.config, .htpasswd and .git), this plugin and its backup folder. Give them only to clients you trust as you would an administrator.
* Confirmations (the second "confirm" step for destructive actions) protect against mistakes and are tied to the exact action and content, but they are not a barrier against the holder of the token: a program using the token can request and use a confirmation in two calls.
* For API clients: refused requests return 403 (file changes outside uploads without fs:admin: xaflo_fs_outside_uploads; with fs:admin they first return 428 with a confirmation token). WP-CLI values go in "args", not in "command".

= 1.6.0 — 2026-09-02 =
* Added: brandable maintenance page — headline, subtitle, label, accent colour, logo and an optional "powered by Xaflo" footer, all from Xaflo → Maintenance. Blank fields fall back to the site's own name and tagline, so a client site is never Xaflo-branded unless you choose to.
* Added: "Preview page" button and `GET /maintenance/preview` — renders the 503 page with the current form values without taking the site down.

= 1.5.2 — 2026-09-02 =
* Fixed: 503 page countdown read "78 s seconds".

= 1.5.1 — 2026-09-02 =
* Fixed: admin Audit Log page failed with "Request failed" and the Maintenance page with "Missing authorization header" — `/audit`, `/maintenance` and `/whoami` only accepted a JWT; they now also accept the logged-in admin (cookie + nonce), like the Dashboard.
* Fixed: the maintenance message is rendered as HTML, as the admin page promises (it was escaped).
* Changed: redesigned 503 maintenance page — animated, with a live countdown from Retry-After and automatic reload when it elapses; honours prefers-reduced-motion; no external assets.

= 1.5.0 — 2026-07-16 =
* Added: content write responses report `content_filtered` / `content_wrapped` / `content_changed`, so a caller knows when KSES altered its markup instead of finding out on the rendered page.
* Added: `content_format` (`raw` | `html`) on post/page create and update. `html` wraps the payload in a core/html block so wpautop cannot break inline `<script>`/`<style>`.
* Added: `DELETE /pages/{id}` (trash, or permanent with the 2-step confirmation token).
* Added: admin About page now lists live REST routes read from the server, with search and filters.
* Fixed (1.4.1): users with `unfiltered_html` save raw markup; everyone else goes through KSES, mirroring the block editor.
* Security (1.4.0): SSRF guard on remote downloads, zip-slip fix in backup restore, forwarded-IP headers honored only from trusted proxies, protected meta guard, `wp-config.php` hard-blocked in the file explorer.

= 1.3.2 — 2026-05-20 =
* Build: WordPress.org distribution variant introduced. This build defines `XAFLO_WPORG_BUILD = true` which disables endpoints classified as disallowed functionality by the WordPress.org Plugin Review Team: WP-CLI execution (`wp_cli_exec`), raw SQL queries (`wp_db_query`), generic filesystem writes (`/fs/write`), plugin/theme file editing (`wp_write_plugin_file` / `wp_write_theme_file`), and any plugin/theme installation, activation, deactivation, scaffold or switch operation. Maintenance-mode drop-in writes are also disabled. The remaining 50 endpoints cover content CRUD, media, SEO, analytics, multilingual, menus/widgets/customizer (read+write via standard core APIs), backup of the plugin's own snapshots, and read-only plugin/theme inspection.
* The full version with all 78 endpoints continues to be distributed at `https://wpconnect.xaflo.com/wordpress-plugin/` as a separate self-hosted build.

= 1.3.1 — 2026-05-17 =
* Improved: extension-aware allowlist on `/fs/write` (only safe text formats — `.php`/binary now require explicit operator opt-in).
* Improved: `Authorization` header falls back to `HTTP_X_WPCB_AUTHORIZATION` for shared hosts that strip the standard header.
* Fixed: `make-plugin-zip.py` was nuking `vendor/firebase/php-jwt/src/` because it excluded `src/` at all depths — split into root-only vs anywhere exclusion lists.
* Fixed: WordPress `sanitize_file_name()` was renaming `.exe.blockmap` to `.exe_.blockmap` — custom sanitizer preserves double-extension artifact names.

= 1.3.0 — 2026-05-16 =
* Added: `FilesystemController` (`/fs/list`, `/fs/read`, `/fs/write`, `/fs/mkdir`, `/fs/delete`) — sandboxed to ABSPATH with `wp-config.php` write block + path-traversal hardening (`realpath` containment, null-byte stripping, `..` rejection).
* Added: text/binary auto-detection for extensionless files (`error_log`, `debug.log`, `LICENSE`, …) with NUL-byte heuristic fallback.

= 1.2.0 — 2026-05-16 =
* Added: `MaintenanceController` — remote toggle with branded 503 drop-in + IP allow-list bypass; admin page server-rendered (works even if React bundle fails).
* Added: `DebugLogController` — tail `wp-content/debug.log` with severity filter; 8 MB tail cap; clear action.
* Added: plugin install / activate / deactivate / read-file / write-file endpoints.
* Added: theme file read / write endpoints.

= 1.1.0 — 2026-05-16 =
* Added: optional self-hosted update channel against `wpconnect.xaflo.com` (off by default in the wp.org build); SHA-256 integrity verification before unpack.
* Added: Plugin Deployer companion in the desktop app — push a `.zip` from your machine to N sites with one click.

= 1.0.0 — 2026-05-15 =
* First stable release. 78 tools, 23 JWT scopes, automatic snapshot before destructive writes, audit log with payload + actor tracking, WPML + Polylang adapters as first-class objects.

= 0.9.0 — 2026-04-22 =
* Added: 10 analytics endpoints (top posts, no-traffic, keywords, referrers, period comparison, category trends).
* Added: WPML + Polylang multilingual adapters.

= 0.8.0 — 2026-03-30 =
* Added: backup + audit subsystem. Automatic snapshot before destructive writes, full audit log, single-call rollback via `wp_backup_restore`.

= 0.5.0 — 2026-01-12 =
* First public preview. 40 tools. JWT auth. Read-only by default.

== Upgrade Notice ==

= 1.7.0 =
Security release with breaking changes. After updating, rotate the signing secret (Xaflo → Settings) and reissue your tokens. site:write now changes only the uploads folder; full file control needs the new fs:admin permission. Database writes need InnoDB tables. A saved IP allowlist is enforced only after you save it again.

= 1.3.1 =
Security hardening on the filesystem endpoint and broader auth-header compatibility. No breaking changes.

= 1.3.0 =
Adds the filesystem controller. If you don't grant `fs:write` scope to any token you are not affected. No breaking changes.

= 1.2.0 =
Adds maintenance mode + debug.log tail + plugin install endpoints. New scopes: `system:maintenance`, `system:debug`. Existing tokens keep working.
