_This documentation is bundled with Skills 0.4.86 and describes the Companion 0.4.117 and Power 0.6.35 release set. Power remains a separate opt-in plugin with server-side gates, real administrator credentials, and local unlocking. The candidate loader runs only already P2-verified MU candidates for a short period. Before manual activation, a signed local loopback verifies exact site origin, timeout, HTTP status, and response body. A run becomes healthy only after wp_loaded. exit(), regular PHP fatals, stale or corrupted markers, and concurrent runs disable every candidate and fail closed. It is not a PHP sandbox and does not promise database or network rollback, or protection from SIGKILL, OOM, segfault, or late shutdown failures. Deferred Power work, including Code Snippets single use, is in the product roadmap. This documentation describes capability boundaries only. It does not assert a promotion, public availability, or a particular sales or update channel._
Up front, honestly: almost nobody needs this mode. For blog posts and landing pages, the verified core (wp-agent-companion) is everything you need. Power mode is a separate, powerful tool for developer tasks. It is fully switched off by default, and you unlock it only deliberately and piece by piece. This documentation explains it fully and does not sugarcoat the risks. If terms like database, PHP, or staging mean nothing to you, that's your signal that you don't need to read on here.
Core statement in one paragraph
Power mode is a second, completely separate WordPress plugin (wp-agent-power) for direct access to files, the database, and PHP execution, fully disabled by default. It is split into tiers, from low-risk reads to arbitrary code execution. P0 can already see confidential data. P1 and P2 are write access, and P2 is already equivalent to running code on the site. P3 bounds changes with a mandatory backup, dry run, row cap, locks, readback, and rollback. Those safeguards are not a general promise that database changes are safe on a live site. P4/P5 is intended only for test copies and needs an additional time-limited approval on detected production. Separately switchable BACKUP and RESTORE channels back up and restore both database and files over REST, without SSH or a local WordPress path.
Public JSON contracts in this documented release set
This section describes the Companion 0.4.117 and Power 0.6.35 release set, bundled with Skills 0.4.86. The Power factory map contains 26 abilities, and the test catalog contains 30 Power contracts. These counts do not by themselves prove that every ability is exposed in every installed profile or provider state. Clients always read the ability schemas of the installed version and must not recreate older open JSON shapes. This section documents its evidenced contracts only. It does not assert a promotion, public availability, or a particular sales or update channel.
Every public Power schema is closed and bounded. Unknown input fields return HTTP 400. When an older optional provider output is absent, it is returned as explicit null, not silently omitted. A provider output that cannot be represented is rejected with HTTP 500 rather than truncated. Where an older Power ability needs an open JSON value, it uses exactly the integrity envelope {json,sha256,bytes}, with no format field. bytes and sha256 bind the UTF-8 JSON bytes. It permits only JSON objects or lists up to 4 MiB, 32 levels, and 20,000 entries.
The backup and restore paths deliberately remain different and fully typed. db-select returns its redacted list in the stated JSON envelope under rows, while columns remains a bounded name list. db-tables returns closed table, column, and DDL metadata. db-export returns columns and rows as a bounded matrix of Base64 cells or null, with batch_sha256. db-import accepts and acknowledges the same structure for begin, batch, commit, or abort. file-catalog returns closed component, directory, and file entries. file-export and file-import transfer content only as bounded content_base64 with SHA-256 and an offset. These shapes do not replace backup gates, encryption, approval, or the server-side drift check.
1. Who this mode is for
The normal, verified core of the toolkit (wp-agent-companion, blog post and landing page processes) covers almost all tasks and checks its own results. Power mode is intended exclusively for tasks that have no matching, verified feature: inspecting a config file, checking the value of a single setting, reading or changing a targeted database row, pulling a plugin list for troubleshooting, running a bounded WP-CLI maintenance command, or (in exceptional cases, on a test copy only) running arbitrary PHP code for debugging. Every result from this channel is explicitly unverified (verified: false, channel: "power" in the CLI output), because it bypasses the checks of the normal processes (render check, SEO persistence, idempotency).
2. Two separate trust domains
The verified core and power mode are deliberately two separate plugins with separate access points (MCP endpoints):
- Core:
<site>/wp-json/wp-agent/mcp(always installed,wp-agent-companion). - Power:
<site>/wp-json/wp-agent-power/mcp(separate add-on pluginwp-agent-power, not pre-installed).
The power access point is not automatically in the assistant configuration. The normal connection setup (wp-agent init) enters only the core server, a deliberate secure default. After server-side activation, wp-agent power init generates the power block for ~/.codex/config.toml or .mcp.json using real administrator credentials. It refuses to silently enable Power remotely and verifies that the server master switch is open. Details are in docs/en/agents/codex-power.md.
The core (wp-agent-companion) contains no raw-access code whatsoever. An automated test (power-firewall.mjs) proves that the normal processes load nothing from the power code and that the core includes no file from wp-agent-power. disable-power.php knows only the plugin name and deactivates the power plugin via the WordPress core interface, without loading power code.
3. The tiers at a glance
| Tier | Name | Capability | Prerequisite to activate | On detected live site |
|---|---|---|---|---|
| P0 | Read | Read a file, list a directory with strict bounds, run a SELECT, read an option, list plugins/themes, environment info | Valid license + master switch | Allowed (with redaction) |
| P1 | Scoped WP-CLI commands | Fixed list of bounded maintenance and read commands | Valid license + master + tier flag P1 | Allowed (bounded and repeatable) |
| P2 | Change files | Write, edit exactly, and delete under wp-content/themes/ and wp-content/mu-plugins/ with crash guard | Valid license + master + tier flag P2 + environment permission | Locked without WP_AGENT_POWER_ALLOW_WRITE_ON_PRODUCTION |
| P3 | Write to the DB | An UPDATE/DELETE or an option set/delete, with a safety envelope | Valid license + master + tier flag P3 | Allowed (no hard environment lock, see section 6) |
| P4/P5 | Arbitrary code | exec-php plus a verified Code Snippets surface, both code execution (RCE) | Valid license + master + tier flag P4 + wp-config constant WP_AGENT_POWER_ALLOW_CODE_EXECUTION | Locked in production without an exact one-time approval |
| BACKUP | Remote backup | Catalog and export the database and regular files in verified chunks | Valid license + master + BACKUP tier flag | Allowed, contains unredacted secrets and requires encryption |
| RESTORE | Remote restore | Atomically restore verified database tables and file components through staging | Valid license + master + RESTORE tier flag | Allowed, destructive and separately unlocked |
Every tier has its own, independently switchable flag (a "flag" here is simply an on/off switch in the WordPress database). A valid WPAgently license and the master switch (wp_agent_power_enabled) are additionally mandatory for every tier; the master is the single, top-level kill switch. A fresh or unlicensed installation registers zero capabilities, no category, and no dedicated access point: "registration as gate" means an unlocked tier simply does not exist server-side (WordPress responds with 404 "not found" instead of a runtime rejection), even for a valid administrator password.
4. The tiers in detail
P0, pure read (six capabilities)
read-file(rel_path): reads a file within the WordPress directory. Path tricks, absolute paths, drive letters, null bytes, and symlinks as a target are locked, as are certain backup and audit directories. Files over 1 MiB are rejected, not truncated. A denylist refuses database dumps (.sql/.sqlite/.db), key material (.pem/.key/.p12/...), the.envfamily, and known secret files entirely, because redaction structurally cannot make them safe. The remaining content is redacted twice:define()constants with names likeKEY,SALT,SECRET,PASSWORD,NONCE, plus a generic pattern redaction across the whole text.list-directory(rel_path, optionalmax_depth,max_entries): lists regular files and directories deterministically withinABSPATH. Depth is limited to 0 through 5 and output to 1 through 500 entries. Symlinks are not followed. Protected backup, firewall, and audit directories remain excluded.db-select(query): runs exactly one SELECT (a pure read query). Multiple statements, SQL comments, and all write keywords are rejected, as areINTO OUTFILE/DUMPFILE/LOAD_FILE. Stateful or intentionally expensive functions such asSLEEP(),BENCHMARK(),GET_LOCK(), and related lock, replication-wait, and locking-service functions are also blocked. The same applies toLAST_INSERT_ID(expr), assignments through@variable := ...,SELECT ... INTO @variable, and locking clauses such asFOR UPDATE,FOR SHARE, andLOCK IN SHARE MODE. In addition to redaction of the output, a check of the query text blocks any reference touser_pass,user_activation_key,session_tokens, towp_users/wp_usermetaas a whole, and tooption_valueor a*select onwp_options. This closes off bypasses via alias,SUBSTRING/CONCAT, orUNIONat the root. Row limit 200. The redacted rows are in the closed{json,sha256,bytes}rowsenvelope, not an open array.get-option(name): reads a single WordPress option. Values with a secret-looking name (auth,api,secret,token,password,key,salt, plus the concretely populated containersmonsterinsights_site_profileandwp_woocommerce_api_keys) are redacted, including recursively inside arrays.list-extensions: lists installed plugins and themes with version and active status, no secrets.env-info: reports WP/PHP version, detected environment type, active theme, and the full power status (active, which tiers are open, the counter, whether writing on production or code execution is allowed via the constant).
P1, scoped WP-CLI commands (one capability: wp-cli)
A fixed, fully anchored list of allowed commands: rewrite flush, transient delete <name>, plugin list, theme list, option get <name>. Everything else is hard-rejected, in particular global cache flushes, cron, db, eval, eval-file, search-replace, user, core, config, shell, server, package, and any mutating plugin/theme subaction. cache flush is explicitly unavailable because it can mutate network, object, or full-page caches outside the backed-up site state. cron event run <hook> is also unavailable because an arbitrary hook can mutate external systems or other state that the P1 path does not back up. Execution runs with no shell in between, so no shell metacharacters can be exploited. On Windows, a native wp.exe is started directly. A discovered wp.bat or wp.cmd launcher is never passed to the Windows command interpreter. Power accepts it only when wp-cli.phar is next to the launcher and then starts the discovered PHP interpreter with that PHAR as a fixed argument. A php.exe next to php-cgi.exe is detected automatically. This covers Local installations without passing paths or arguments containing percent signs, exclamation marks, quotation marks, spaces, or accented characters through cmd.exe. Because anonymous proc_open pipes do not provide reliable non-blocking reads on Windows, Power captures standard output and standard error there through automatically deleted temporary file handles with the same hard size and time limits. If the adjacent PHAR or the PHP CLI interpreter is missing, WP-CLI fails closed with a specific diagnostic. The output of option get is redacted in two stages.
With background:true or --background, the same validated command can be queued as a persistent WordPress Cron job. The first call immediately returns an unguessable job ID. With job_id or --job-id, only the same administrator can read its status and result. Immediately before execution, the worker rechecks the licence, master switch, P1 flag, administrator capability, and allowlist. Output is converted to valid UTF-8 and redacted with the synchronous path's rules before it is stored. Each site stores at most 20 queued, running, or retrievable jobs. Active jobs are never displaced. At capacity, only the oldest completed result is removed. Results expire after one hour. A job is marked failed after 15 minutes without starting or after two minutes in the running state. If DISABLE_WP_CRON is enabled, Power does not start an internal Cron request and leaves the persistent event for the operator's external scheduler.
P2, change files and inspect extension archives (seven capabilities)
All three capabilities operate exclusively under wp-content/themes/ and wp-content/mu-plugins/, with a full path guard. write-file replaces the complete content. edit-file requires the previously read SHA-256 checksum and replaces exact text. Multiple matches are rejected unless replace_all:true is supplied deliberately. delete-file requires the same conflict check and additionally requires confirm:true. A server-side short-lived lock prevents concurrent changes to the same file, including direct REST or MCP calls.
Each named P2/P3 path uses its own snapshot, pre-mutation audit entry, and exact read-back. PHP writes are checked with php -l first. Home and wp-admin are then probed. Only while the path can still prove its exact request-owned intermediate state may it attempt its documented restoration and verify it. On foreign drift, lock loss, or ambiguous read-back, it preserves the current state and returns recovery_required. The size cap is 2 MiB. On detected production, none of the seven capabilities is registered server-side unless the wp-config constant WP_AGENT_POWER_ALLOW_WRITE_ON_PRODUCTION is set.
The three additional capabilities prepare-extension-archive, inspect-extension-archive, and revoke-extension-archive provide a safe dry-run inspection for a local plugin or theme ZIP. Bytes are streamed through a five-minute one-time endpoint. The 256-bit token exists only in a header and is stored server-side only as a SHA-256 hash. The server accepts at most 32 MiB into a random directory outside ABSPATH and wp-content, and it requires actual ZIP content. Inspection extracts nothing. It rejects traversal and absolute paths, duplicate names, symlinks, special files, encrypted entries, more than 2,000 entries, individual files larger than 32 MiB, more than 128 MiB total uncompressed data, and suspicious compression ratios. Exactly one package root and exactly one unambiguous plugin main file, or a theme style.css with a valid WordPress name, are required. The temporary copy is deleted after success or rejection. A scheduled cleanup also removes abandoned sessions.
The seventh capability, verify-manual-extension-install, is read-only. It fully compares an active or inactive extension that an administrator installed manually with the short-lived private file attestation from archive inspection. It does not install, activate, deactivate, delete, or change anything, and it still requires the open P2 gate and Administrator privileges.
This path deliberately installs and activates nothing. WordPress cannot give a general, reliable transaction guarantee for an arbitrary third-party extension, and plugin activation would execute foreign PHP. Inspection therefore returns installable:false and dry_run_only:true. A backup and the SCHREIBEN phrase are unnecessary for this non-mutating inspection. A future transactional install path must enforce both before mutation. Until then, actual installation remains a deliberate administrator action under Plugins > Add Plugin or Appearance > Themes.
P3, write to the database (two capabilities: db-write, option-write)
In addition to the protected-entity checks, every db-write target must belong to the current site's verified table catalog. Every site's options tables and Multisite network tables remain blocked. A site administrator therefore cannot write another Multisite site or a nested WordPress installation. Blocking, stateful, and intentionally expensive SQL primitives are forbidden on the write path as well. The preview cap and the detection of WHERE, ORDER BY, and LIMIT distinguish statement structure from string literals and quoted identifiers.
db-write runs exactly one UPDATE or DELETE; a WHERE clause is mandatory; dangerous SQL constructs and subqueries are locked, as are multiple statements and SQL comments. Protected areas (wp_users, wp_usermeta, the complete options table, firewall/security tables, the audit table, all wp_agent-owned tables, MySQL system schemas) are hard-locked, both as the target table and as a reference anywhere in the statement. A row cap (default 500, checked in advance via a preview SELECT) prevents mass damage. Preview instead of execution is the default (dry run): without dry_run:false, the capability returns only a redacted preview of the affected rows. A real UPDATE or DELETE requires a recognizable primary key and a transactional table. Under a server-side table lock, the target rows are captured in a database transaction. MySQL and MariaDB use FOR UPDATE. SQLite uses the transaction and the fixed primary-key set; a conflicting external write produces a fail-closed database error. Only this fixed primary-key set is then mutated, never the potentially changed WHERE predicate again. LIMIT without ORDER BY is rejected as nondeterministic. Before commit, the changed or deleted row count must exactly match the persisted read-back; a mismatch rolls the transaction back. After commit, the same health-probe-plus-verified-restore mechanism as P2 runs. Only a proven success increments the internal epoch counter.
option-write sets or deletes exactly one option, exclusively via the official WordPress functions (never via raw SQL). A denylist protects the tool's own master/tier/counter switches, siteurl/home (environment detection), firewall configuration, and a fixed list of security-critical options (admin_email, new_admin_email, users_can_register, default_role, template, stylesheet, current_theme, active_plugins, active_sitewide_plugins, upload_path, upload_url_path). Additionally: a new value may not be an array/object, and an already serialized stored option (such as wp_user_roles) may neither be overwritten nor deleted. A server-side lock protects the same option target. The capability honors the actual update_option() or delete_option() return value and rereads the raw persisted value. Filter vetoes, transformed values, and later drift return an error and use a compare-and-swap rollback instead of reporting false written:true or incrementing the epoch. An identical value or deleting an absent option remains an explicit no-op. Dry-run default, redacted diff, the same health-probe-plus-restore mechanism.
The compare-and-swap compares persisted bytes rather than the database's potentially case-insensitive collation. If the WordPress API returns false, WPAgently does not claim the newly visible state as its own mutation and does not roll back a possible foreign winner.
P4/P5, arbitrary code execution (four capabilities)
Executes a supplied PHP code block in the WordPress runtime context and returns the return value, output, and errors. This is running arbitrary code with no content restriction, deliberately so: restricting what arbitrary code is allowed to do would not be technically enforceable. All the security therefore lives before execution, in a five-part registration gate:
- Plugin active.
- Valid WPAgently license.
- Server master switch on.
- Tier flag
wp_agent_power_stage_p4on. - wp-config constant
WP_AGENT_POWER_ALLOW_CODE_EXECUTIONstrictly=== true(no "truthy" comparison; a string"true"or"1"does not count).
If any of the five conditions is missing, exec-php is simply not registered (404, no tool). The constant is the decisive factor against a web-only escalation: it lives in a file on the server and cannot be set via any web/ability path; a leaked administrator password cannot set it. In addition, the capability requires manage_options and checks the gate a second time at runtime. On detected production, a valid, non-expired production override token is additionally required (see section 6). Before every execution, a tamper-evident audit entry is written; without a successful log entry, the code does not run. Important: exec-php creates no reversible prior state server-side and restores nothing automatically. Arbitrary code can trigger unlimited side effects, which is why the health probe afterward is only a crash indicator, not a guarantee.
The three additional capabilities list-code-snippets, get-code-snippet, and mutate-code-snippet use the public PHP API of the Code Snippets plugin. The list deliberately omits source code. A targeted read returns the code, its type, and a conflict hash. The mutation can create, update, activate, deactivate, or soft-delete local, non-network PHP, HTML, CSS, and JavaScript snippets. Create and update always leave the snippet inactive. An active snippet must be deactivated before it can be edited or trashed. Changing between snippet types requires explicitly resubmitting the source. Every mutation requires an action-specific exact confirmation, a fresh hash for an existing snippet, intent and a backup reference, writes the audit entry before calling the provider, reads the actual provider state back, and increments the epoch counter. PHP runs through php -l before saving and activation. Every activation probes the site afterward. A detected hard error or invalid provider read-back triggers a best-effort verified rollback.
Current production contract: the reusable production override is removed and deliberately incompatible. On detected production, exec-php and every Code Snippets mutation require a fresh one-time approval. The server binds it to the blog, administrator, exact action, payload and intent hashes, expiry, and nonce; audits mint and consume separately without the secret; and consumes it atomically before code or mutation. The CLI requires a second confirmation, keeps the ticket only briefly in a mode-0600 file, and never passes or prints it through argv or stdout.
This specialization is not a lower trust tier. A successfully activated snippet runs with the site's privileges on subsequent WordPress flows or injects HTML, CSS, or JavaScript into the response. Both read operations therefore remain behind the full P4 gate as well. Snippet source can also contain credentials. Network-wide and shared multisite snippets, single-use snippets, and snippets bound to Code Snippets Pro conditions remain unsupported. Inputs and existing provider data with invalid UTF-8 are rejected unchanged so that the conflict hash cannot collapse distinct byte sequences. The contract also covers invalid provider results and verified rollbacks. The full lifecycle of all four supported types is practically tested against a real local Code Snippets 3.9.6 installation. This documentation describes the evidenced capability boundary of the Companion 0.4.117 and Power 0.6.35 release set, bundled with Skills 0.4.86. It does not assert a promotion, public availability, or a particular sales or update channel.
BACKUP and RESTORE, complete remote protection
The standalone BACKUP tier (wp_agent_power_stage_backup) carries the backup path over REST for customers without SSH or WP-CLI access. db-tables first returns a discovery catalog and then materializes the selected scope inside one database read transaction. MySQL and MariaDB use REPEATABLE READ with a consistent snapshot and metadata locks that keep table schemas stable. SQLite pins its MVCC read view on the first access. db-export subsequently reads only the immutable private snapshot files in resumable, checksummed chunks. Changes between HTTP requests can therefore no longer produce a mixed backup. The snapshot ID and random token are bound to the administrator and blog, stored only in the private local progress file, and never sent in a URL. The client deletes the session after success, while the server cleans expired or abandoned sessions. A session is bounded to 512 tables, 512 MiB, 120 seconds, at most three concurrent sessions, and a two-hour lifetime. Tables without a primary key are supported only up to 10,000 rows in one stable read; larger tables fail closed. file-catalog records regular files in allowed WordPress components and file-export transfers them in size-limited chunks with SHA-256 checksums. Links, special files, protected directories, and catalogs above 20,000 files are rejected.
The file path does not create an unbounded server archive. It transfers at most 512 KiB per chunk, verifies every chunk and the complete file with SHA-256, and creates the tar.gz only in the local backup directory. The catalog and file restore accept at most 20,000 files and directories combined.
Before a local tar.gz restore, the normal and verbose archive listings are processed line by line and are never buffered in full. An archive may contain at most 200,000 entries, 4 KiB per path, 64 KiB per output line, and 128 MiB of listing output. Exceeding any limit is rejected before extraction.
Unlike db-select, db-export is lossless and does not redact data. Password hashes, session tokens, and secret options can be present in the export. A valid WPAgently license, its own off-by-default flag, the manage_options requirement, and mandatory client-side encryption protect this path. If a sensitive table is included (wp_options, wp_users, wp_usermeta), the backup client aborts without a passphrase. An escape hatch exists only through the deliberate allowUnencryptedSensitive option.
For encrypted REST backups, JSONL rows are bound with AES-256-GCM. The progress file, completed table artifact, and manifest also carry an HMAC derived locally from the passphrase with scrypt. The CLI verifies this file authentication before a restore requests any server mutation. The passphrase and derived key never leave the local client.
The separate RESTORE tier (wp_agent_power_stage_restore) accepts only locally verified manifests. Database imports first land in session-bound staging tables and replace selected targets only on a complete commit. File imports land in adjacent staging directories and are swapped only after size and checksum verification. Failures trigger rollback or a hard rejection. --tables actually scopes REST database restores. It is rejected for monolithic local dumps, where selective restoration cannot be implemented losslessly.
A restorable REST database backup uses the versioned wpagent-engine-ddl-v2 schema format. It includes the engine-native table definition, separate SQLite indexes, constraints, the official SQLite Integration's MySQL type metadata, and the auto-increment state. Older data-only artifacts are rejected before the first server write. Views, virtual tables, non-transactional MySQL tables, and tables with triggers are likewise rejected during backup. WPAgently does not silently omit those objects or label an artifact as a complete database backup when it cannot restore it later.
Missing tables may be created only with explicit restore approval inside the current site's prefix. Other multisite blogs and detected nested WordPress installations remain outside that boundary. Existing targets must match the complete backed-up schema both at the start and immediately before commit. Foreign-key checks are paused only on the same database connection, their state is read back, and every error path re-enables them. Before reporting success, Power verifies every constraint whose child or parent table is affected by the restore.
A schema difference between begin and commit is drift and is rejected fail closed with HTTP 409 before destructive live mutation begins.
A previously missing SQLite table remains completely invisible during begin and every batch. Only commit creates the table, indexes, and type metadata inside the same SQLite transaction, copies the verified data, restores the sequence, and reads back the schema, indexes, foreign keys, and sequence. Any failure rolls back the complete creation. A table created concurrently by another process is rejected without modification and is never deleted by abort.
MySQL and MariaDB cannot include schema DDL in the same data transaction. On those engines, Power creates explicitly authorized missing InnoDB targets only immediately before the data transaction while foreign-key checks are paused and read back. Every successful creation is recorded in the session journal. The following transaction locks and revalidates every target schema, clears existing targets in a separate phase, then writes parent, child, and cyclic tables independently of manifest order and verifies every affected foreign key. A controlled failure removes only newly created targets that are still empty and schema-identical, as one related group. Because MySQL DDL is non-transactional, a hard process interruption in the narrow interval between table creation and journal update can leave an empty target table. Resume treats it as a foreign concurrent creation and fails closed with HTTP 409 instead of deleting it without unambiguous ownership evidence.
For existing MySQL and MariaDB tables, Power checks the backed-up auto-increment value against the staged IDs before any live mutation. An impossible value is rejected while the live data is still unchanged. Because of MySQL DDL semantics, the final ALTER TABLE remains a step after the atomic data commit. If only that step or its read-back fails, Power no longer reports a restore that supposedly rolled back. The session is stored idempotently as applied, and the response contains an explicit finalization_warnings warning for the unconfirmed auto-increment state.
5. Additional security mechanisms (cross-tier)
- Protected areas (
protected-entities.php): the mode cannot rewrite its own gate. Every option with the prefixwp_agent_power_(master, all tier flags, the counter, and short-lived production approvals) is unreachable viaoption-write/db-write.siteurl/home(the basis of environment detection) likewise, so a real live site cannot disguise itself as a test copy. - Environment detection (
wpagent_power_environment()): three categories,production/staging/local(live site, test copy, local). Fail-safe: any host not clearly detected as local counts asproduction. Local is detected via loopback addresses and reserved dev extensions (.local,.test,.example,.invalid), otherwise viawp_get_environment_type()or theWP_ENVIRONMENT_TYPEconstant. - Block markup protection, CVE-2026-7465 (
block-guard.php): prevents a database or file write from injecting a malicious, attacker-controlled Gutenberg block intopost_content(which would become code execution given a vulnerable block registration). Strict fordb-write(only a fixed list of known content blocks,core/htmlandcore/freeformalways forbidden), deliberately looser for P2 file writes, because P2 already has full theme code access anyway. - Crash guard and health probe: P2 checks written PHP content with
php -land records the prior file state. P3 records the affected database rows. Both then check home andwp-adminvia loopback. Only the named path may attempt its documented restoration while its own intermediate state remains exactly provable. P4/P5 has no restoration promise because arbitrary code can have unlimited side effects. Foreign drift, lock loss, and ambiguous state remain for manual review and returnrecovery_required. Important, stated plainly in section 9: this is a crash detector, not a security guarantee. - Audit log (
audit.php): a server-side, fail-closed log in the stable Power control directory. By default, this is an installation-specific directory besideABSPATH, outsideABSPATHand every restorable component. If the host does not allow writes there, Power uses the fallbackwp-content/.wp-agent-power-control/audit/. Both locations receive Apache, IIS, and PHP guards. Nginx does not interpret those portable files itself. For a WordPress subdirectory installation behind Nginx,WP_AGENT_POWER_CONTROL_DIRmust therefore point outside the document root or direct access must be denied in the hosting configuration.WP_AGENT_POWER_CONTROL_DIRcan pin another absolute location inwp-config.php. Sequence, links, SHA-256, and the HMAC made with an audit-specific, PHP-guarded key must all match. A separate signed tail state, bounded to 4 KiB, binds the last sequence, last hash, and file metadata. The normal append path validates that state and the actual file tail in constant time. If the tail state is missing, it is rebuilt only after a complete authenticated verification of the chain. Corrupt, oversized, or linked state files fail closed. The key file has mode0600, lives in the same non-restorable control directory, is retained with the forensic history during uninstall, and is never written to the audit, checkpoint, or tail state. Before migration, an existing v2 history is completely verified with its former key derived from the WordPress security constants. Only then is that exact key persisted. Later WordPress salt rotation and plugin reinstallation therefore do not interrupt the audit chain. If the persistent key is missing after salt rotation or is corrupt, Power does not begin a new history and instead fails closed. Modification, deletion, reordering, invalid UTF-8, and one-sided truncation also fail closed. New entries store neither the login nor host in clear text, only local HMAC pseudonyms. An existing legacy v1 chain is fully verified before adoption and anchored by the first v2 checkpoint. Because v1 did not have an HMAC, that anchor proves only the state found during the upgrade. It cannot prove the authenticity of a v1 history that an earlier filesystem actor may have recalculated completely. An entry is written before every P1/P2/P3/P4 write operation runs. Since 2026-07-13, this also applies to BACKUP, with metadata only and never row data. Pure P0 reads remain unaudited. The local limit remains: an actor who controls the audit, checkpoint, tail state, and key file together can remove or replace this purely local trail. P4/P5 can read or modify all local control artifacts. Only external or immutable logging can close that trust boundary. - Stable restore state: restore sessions and the installation-wide
.session.locklive in the same control root as the audit, outside every component that RESTORE may replace. A complete uploads swap therefore cannot remove that state. Legacy sessions under uploads are migrated into the control root while holding an exclusive lock. Waiting for a central lock is bounded to about 500 ms. Power then rejects the operation fail closed instead of blocking a PHP worker indefinitely. - Kill switches: server-side, the core capability
wp-agent/disable-powerdisables the entirewp-agent-powerplugin via the WordPress core interface; from the next request on, no power capability is registered anymore. Client-side, the environment variableWP_AGENT_POWER_DISABLEDaborts everywp-agent powercommand immediately; this protects only the CLI, not a direct web call. - Advisory locks:
lock.mjsprevents two parallelwp-agent powerruns for the same site on the client. P2 file mutations additionally have an atomic, short-lived server lock per target path. P3 has corresponding server locks per table or option name, anddb-writecombines its table lock with a database transaction and row locks. These server locks also cover direct REST and MCP calls through the plugin. External code does not have to honor them, so P3 additionally uses exact read-backs and compare-and-swap rollbacks. Locks replace neither the gate nor the mandatory backup.
6. Security framing: bounded safeguards for P0 through P3, test-copy-first for P4/P5
P0 is read-only and P1 is limited to bounded, repeatable maintenance and read commands. P2 and P3 require a backup and documented path-specific recovery checks, but are not a general promise of live-site safety. Arbitrary PHP code (P4/P5, the code-execution tier) remains test-copy-first with a separate typed one-time approval.
In practice, this means:
- P0 through P1: no environment lock, usable on any environment (P0 is purely read-only, P1 is limited to bounded and repeatable maintenance and read commands).
- P2: a hard lock on detected production. The capability is not registered at all on
productionunless the operator deliberately sets the wp-config constantWP_AGENT_POWER_ALLOW_WRITE_ON_PRODUCTION. - P3: deliberately no hard environment lock. A mandatory backup, dry-run default, row cap, protected areas, SQL blocklist, and DB error guard with rollback bound the path instead. No one-time approval is required. These mechanisms replace neither a test copy nor an expert review and are not a general live-site safety guarantee.
- P4/P5: stays test-copy-first. On detected production,
exec-phpand every Code Snippets mutation refuse without a server-minted one-time approval. It expires after 15 minutes, is atomically consumed before the action, and is bound to the blog, administrator, exact action, payload and intent hashes, and nonce. Only the secret hash is stored. The mint route is not an ability and the CLI handles the ticket only through a short-lived private file, never argv or stdout. The approval protects against accidental production runs, not against a fully compromised administrator account. - BACKUP: no environment lock (purely read-only, does not change the site), but its own confidentiality lock via the separate flag.
7. Unlocking: the multi-gate
A power capability only runs if all conditions applicable to its tier are satisfied simultaneously:
- Server master switch (
wp_agent_power_enabled, operator authority). Off by default. Administrators enable it under Tools > WPAgently Power or through WP-CLI. - Tier flag per tier (
wp_agent_power_stage_p1/p2/p3/p4/backup/restore, each its own off-by-default switch), always applies in addition to the master. The admin page presents every tier separately with its risk notice. Turning off the master also closes every tier. - wp-config constants (only for P2 on production and for P4/P5):
WP_AGENT_POWER_ALLOW_WRITE_ON_PRODUCTIONorWP_AGENT_POWER_ALLOW_CODE_EXECUTION, settable only via file access towp-config.php, outside the reach of any ability. - Local unlock session (client-side, CLI):
wp-agent power unlockfirst requires a summary (site, detected environment, active tiers) and then either the typed phraseENTSPERRENor, non-interactively, the flag--i-approve-power. Default time limit 30 minutes (--ttl-minutes), can be ended early withwp-agent power lock. The session is bound to the target site, file permissions 0600, contains no credentials. - Per-action confirmation for every write or execute tier, each with its own phrase, so that blindly typing through one does not also confirm another:
- P2/P3: phrase
SCHREIBEN(or--i-approve-power). Without confirmation, P3 automatically stays on the safe dry run. - P4/P5 (
exec-php): its own, stronger phraseCODE-AUSFUEHREN(deliberately written without an umlaut, because it is an exact token to type, not prose). No dry-run fallback: without confirmation and without an interactive terminal, it is refused entirely. - Code Snippets mutations through MCP: depending on the action,
CREATE CODE SNIPPET,UPDATE CODE SNIPPET,ACTIVATE CODE SNIPPET,DEACTIVATE CODE SNIPPET, orTRASH CODE SNIPPET. The server capability additionally requires intent, a backup reference, and the fresh conflict hash for an existing snippet. - Confirming production code: a third, separate phrase
PRODUKTION-CODE-AUSFUEHREN. The CLI then automatically mints an approval only for that action.
- P2/P3: phrase
These phrases are operational confirmations. They are not electronic signatures or evidence of legally informed consent; see section 14.
- Mandatory backup before P2, P3, and P4/P5 (see section 8), enforced purely client-side. P1 is limited to a fixed list of bounded, repeatable maintenance and read commands and requires no backup. Arbitrary Cron hooks are therefore not part of P1.
All commands except lock require real administrator credentials (WP_AGENT_ADMIN_USER/WP_AGENT_ADMIN_APP_PASSWORD or --admin-user/--admin-app-password), never the Editor bot of the verified processes. Server-side, every power capability requires manage_options; the bot user deliberately does not have this and is rejected with 403.
Note on the confirmation phrases: they are literal strings the CLI checks for and must be typed exactly as shown (ENTSPERREN, SCHREIBEN, CODE-AUSFUEHREN, PRODUKTION-CODE-AUSFUEHREN). They remain untranslated here to match the actual CLI behaviour. They support operational discipline but are not electronic signatures or evidence of legally informed consent.
8. Mandatory backup before P2, P3, and P4/P5
For P2 (write-file, edit-file, delete-file), P3 (db-write, option-write), and P4/P5 (exec-php), the CLI enforces a fresh backup before the actual action runs, otherwise the operation is refused without anything being written:
- P2 requires a file backup. P3 requires a database backup. P4/P5 requires both database and files. If the required backup is missing,
--backup-nowcreates it automatically. WithWP_AGENT_WP_PATH, the CLI uses the local snapshot path. Without a local WordPress path, it uses the complete REST path. - P4/P5 additionally requires that the backup was created within the current unlock session (timestamp after the session start), stricter than P2/P3, because with arbitrary code execution, any older snapshot is potentially already outdated.
- A backup only counts as "fresh" if its manifest actually reports database or file content as included, not merely on the existence of a backup folder.
- A REST backup is restorable. Before writing, the CLI verifies the manifest, checksums, target site, and database engine, then uses the separately enabled RESTORE channel.
Important: this mandatory backup is a pure CLI discipline. The server-side capabilities themselves have no notion of backups (backups live on your machine) and enforce nothing server-side. A direct web call that bypasses the backup mechanism is not slowed down by this; see section 9.
9. The honest limits (not sugarcoated)
These limits are documented as such in the code itself and were confirmed in the adversarial security review. They belong, unchanged, in every piece of customer communication:
- P2 is already the code-execution boundary, not just P4/P5. Writing a PHP file to
wp-content/mu-plugins/means executing arbitrary code. That's why P2 is locked on detected production. The constant intended for P4/P5 mainly protects against an attacker without file access (for example, only a leaked administrator password); an attacker who already has P2 access can set the same constant via anmu-pluginand thereby escalate to P4/P5, which grants them nothing new relative to the P2 access they already have. - Backup, unlock session, and the typed per-action confirmation are operational discipline, not a security control. They run exclusively in the CLI. A direct web call against the power access point (for example with a leaked administrator password) bypasses them completely; this was confirmed live in the adversarial review. The actual security boundary is exclusively the server-side gate (valid license, master switch, tier flags,
manage_options, production lock, protected areas). - The health probe is a crash detector, not a security guarantee. It only checks whether home and
wp-adminrespond with HTTP under 500. A malicious but non-crashing write (for example a hidden backdoor inmu-plugins) counts as "healthy" and is not rolled back. This was demonstrated live in the review. - Redaction is best-effort, not a guarantee. It is based on name patterns.
db-export(the BACKUP tier) explicitly provides no redaction at all; that is a deliberate but confidentiality-critical decision, see section 4. - P4/P5 can disable any of its own barriers. Once arbitrary code runs, it can delete or falsify the audit log, disable the kill switch, and rewrite any protected area. No runtime mechanism can prevent this. The entire security of P4/P5 lives before execution, not during or after.
- A synchronous
exec-phpcall already in progress cannot be stopped mid-execution. PHP requests are synchronous; a plugin deactivation in a parallel request only takes effect from the next request onward. - Prompt injection remains a real risk within an open session. Content read via
db-selectorread-fileis data, not instructions. A manipulated assistant can, within an open session, abuse any currently unlocked capability, bounded by the server-side gates of the respective tier (row cap, protected areas, redaction), not by an additional per-action intelligence check.
10. Threat model: what holds up, what doesn't (yet)
Two adversarial security reviews exist, summarized here from the report files and cross-checked against the current code:
P0 through P3 (as of 2026-07-10): the gate's self-protection held up against every direct attack (no way to set the master switch, unlock a tier, or downgrade the environment detection, neither with leaked administrator access nor via prompt injection). Eleven concrete gaps were found (F1 through F11), including three rated "high": db-select could be bypassed via alias/SUBSTRING/UNION and leaked password hashes (F1), read-file leaked any secret not stored as a define() (F2), and option-write allowed writing security-critical options such as wp_user_roles or active_plugins (F3). In the current code, for each of these findings there is a countermeasure explicitly referenced in a comment. This mapping is a code inspection, not a fresh adversarial test run; there is no updated findings report dated against today's code that confirms the fixes, and the checklist still has only unchecked boxes.
P4/P5 (as of 2026-07-11): The gate reviewed at the time, consisting of the master, tier flag, wp-config constant, and production override, held up completely, including live against a simulated leaked administrator password. One finding (F8 regression) was fixed via commit f456bfc and cross-read and confirmed in the current code. Since version 0.6.1, the valid Companion license is an additional registration condition.
BACKUP (db-tables/db-export): built after the other reviews and therefore not covered by them. A dedicated adversarial review has existed since 2026-07-13, with four confirmed findings, all LOW, none crossing a trust boundary defended by WordPress. The hardening implemented and the product decisions left open are summarized in section 16.
11. CLI quick reference
wp-agent power status Environment, server master, session
wp-agent power init Generate the Power MCP configuration
wp-agent power unlock [--ttl-minutes 30] [--i-approve-power]
wp-agent power lock
wp-agent power read-file --path <rel> P0
wp-agent power list-directory --path <rel> P0
wp-agent power get-option --name <opt> P0
wp-agent power db-select --query "<SELECT ...>" P0
wp-agent power list-extensions P0
wp-agent power env-info P0
wp-agent power wp-cli --command "<allowlisted command>" P1
wp-agent power wp-cli --command "<allowlisted command>" --background
wp-agent power wp-cli --job-id <id> P1 job status/result
wp-agent power write-file --path <rel> --content-file <file> \
[--i-approve-power] [--backup-now] P2
wp-agent power edit-file --path <rel> --expected-sha256 <hash> \
--search-file <file> --replacement-file <file> \
[--replace-all] [--i-approve-power] [--backup-now] P2
wp-agent power delete-file --path <rel> --expected-sha256 <hash> \
[--i-approve-power] [--backup-now] P2
wp-agent power inspect-extension-archive --archive <file.zip> \
--type <plugin|theme> P2, inspection only
wp-agent power db-write --query "<UPDATE/DELETE ... WHERE ...>" \
[--i-approve-power] [--backup-now] P3 (dry-run default)
wp-agent power option-write --name <opt> [--value <json>] [--delete] \
[--i-approve-power] [--backup-now] P3 (dry-run default)
wp-agent power exec-php --code-file <file> --intent "<text>" \
[--i-approve-power] [--i-approve-production-code] \
[--backup-now] P4/P5 (no dry run)
Common options: --site, --admin-user, --admin-app-password, --session-dir, --json. Full flag reference including backup options: wp-agent power --help.
12. When to use this mode, when not to
Use it for: targeted diagnosis without a matching core capability, a bounded WP-CLI maintenance or read command, a single, well-scoped file or database change with backup and review, and, as an exception, arbitrary PHP code for debugging exclusively on a test copy or local.
Don't use it for: blog posts and landing pages (use wp-article/wp-landing via the verified core for that instead), arbitrary code on a site detected as production without a compelling reason and without deliberately confirming the production operation, the complete, unredacted database export outside of an actual backup effort, and never to flip the server master switch "just to make a task work."
13. Maturity and shipping status
P0 through P5 plus BACKUP and RESTORE are implemented in Power 0.6.35. This document is bundled with Skills 0.4.86. The test catalog contains 30 Power contracts. It describes their evidenced capability boundaries only and does not assert a promotion, public availability, or a particular sales or update channel. Remote backups contain both database and files and are restorable through the same verified transport. Three P2 abilities upload plugin and theme ZIPs temporarily, inspect them within strict bounds, and revoke the session. They do not extract, install, update, overwrite, or activate an extension. Actual installation remains an explicit administrator action in WordPress. The candidate loader runs only previously P2-verified MU candidates on an opt-in, time-bounded basis. Before every manual activation, a signed local loopback checks only the bound WordPress response. A run is marked healthy only after the wp_loaded WordPress milestone. exit(), PHP fatals, stale or corrupted markers, and concurrent runs disable every candidate and fail closed. This is not a PHP sandbox and cannot roll back database or network effects. The Code Snippets surface does not cover single-use or Pro conditions. That future path is explicitly in the product roadmap. Power stays opt-in. Plugin, server-side gates, real administrator credentials, and local unlocking remain separate prerequisites.
When the Power plugin is deleted, WordPress removes all master and tier grants, unconsumed one-time production approvals, the state epoch, persistent WP-CLI jobs and their scheduled events, orphaned file and database locks, and the update cache. The audit log and existing restore sessions are deliberately retained so that uninstallation cannot destroy forensic evidence or a recovery state that may still be needed. Delete them separately after the operational review if they are no longer required.
14. Legal status
The EULA was updated on 2026-07-30 against the current Power code, documented security limits, and current primary sources. Typed phrases are treated as operational discipline, not as a signature or liability waiver. The former product question about an additional BACKUP constant is resolved in section 16.3. The texts have not been reviewed by a lawyer and must not be presented as attorney-reviewed.
15. Legal
The binding legal framing belongs exclusively in the German EULA (docs/legal/eula.md, clause 6; English convenience translation: docs/legal/en/eula.md, section 6, "Development and Staging Mode"), not in this technical document. This documentation is not legal advice or a compliance certification. It describes the current technical state so that customer docs, marketing, and the EULA can build on the same factual basis.
16. BACKUP security review (2026-07-13): implementation, recommendations, installation
On 2026-07-13, the BACKUP tier (db-tables/db-export) underwent its own dedicated adversarial security review for the first time. Result: four confirmed findings, all LOW, none crossing a trust boundary defended by WordPress. The original code findings are fixed. The optional additional gate was resolved as a product decision on 2026-07-29.
16.1 Hardening implemented (in the code)
- Metadata audit for
db-tables/db-export(finding 2). Both capabilities now write a fail-closed, chained entry to the same audit log as the write tiers before their operation. Only metadata is logged (capability, table name, paging mode, the opaque cursor, and the row limit). Row data or secrets never end up in the log. If the entry cannot be written, the export does not run. This means even the sole unredacted read surface now leaves a forensic trail. - Confidentiality note in the tool description (finding 2). The tool annotation for
db-export(and analogouslydb-tables) previously carried onlyreadonly: true, which an assistant could read as harmless. Atitlenote was added that carries the confidentiality (lossless, unredacted export including password hashes and secret options) through to the tool list.
16.2 Table isolation in shared databases (finding 4)
The original pure prefix filter has been replaced. The catalog recognizes nested WordPress installations by their own set of options, posts, and postmeta tables and excludes their entire prefix. On multisite, global tables also remain unavailable to ordinary site administrators. The integration tests create a foreign installation under wp_tenant_ and verify rejection by both the catalog and direct export.
The remaining technical boundary is explicit. A foreign non-WordPress application that deliberately creates tables inside the WordPress prefix namespace in the same database has no WordPress structure markers and cannot be identified automatically as foreign. Such tables must use a separate, non-nested prefix. This prefix separation is an operational requirement for shared databases.
16.3 Gating decision for remote backups (finding 1, decided on 2026-07-29)
Unlike P2 (environment lock) and P4/P5 (additionally the wp-config constant WP_AGENT_POWER_ALLOW_CODE_EXECUTION), BACKUP has no filesystem constant. Its registration gate checks the valid WPAgently license, the master switch, and the backup flag. A permanently set backup flag therefore means any manage_options credential on a licensed site can pull the entire database in plain text.
The additional file-only wp-config constant WP_AGENT_POWER_ALLOW_DB_EXPORT will not be added. It would prevent a leaked admin application password from opening the backup gate by itself, but it would also make the intended REST backup path unusable for customers without SSH, WP-CLI, or hosting file access.
This trade-off is acceptable because a manage_options credential can already install plugins under WordPress's native security model and therefore fully compromise the site. BACKUP does not cross an additional WordPress trust boundary. The unredacted export still remains tightly controlled by a valid WPAgently license, the master switch, a dedicated off-by-default flag, manage_options, no ability registration while the gate is closed, mandatory encryption for sensitive tables, and fail-closed metadata auditing. Operators should open the backup flag only for the backup run and close it immediately afterward.
16.4 Installing and activating the wp-agent-power plugin
The power plugin is a second, separate WordPress plugin and is not included in the Companion package. The release build produces it as its own artifact dist/wp-agent-power-<version>.zip. Installation and unlocking:
- Upload and activate the ZIP
dist/wp-agent-power-<version>.zipunder Plugins > Add New > Upload Plugin. The folder and file namewp-agent-power/wp-agent-power.phpmust be preserved, otherwise thewp-agent/disable-powerkill switch does not take effect. - After activation, nothing is unlocked yet. A fresh installation registers zero capabilities and opens no access point (registration as gate). Nothing happens until the master switch and the respective tier are deliberately set.
- Under Tools > WPAgently Power, enable the master switch and BACKUP. Leave RESTORE off until a restore is actually required. Alternatively, use WP-CLI:
wp option update wp_agent_power_enabled 1
wp option update wp_agent_power_stage_backup 1
Turning off the master switch in the admin page also closes every tier flag. The WP-CLI equivalent is wp option delete wp_agent_power_enabled.
16.5 Release evidence
The full power test run includes backup-rest.mjs, the security regression tests for all tiers, the audit chain test, and the advisory lock test. The project build includes wp-agent-power, this power skill, and the agent documentation in the release artifacts. Before every release, npm run test:power and node scripts/build.mjs must run successfully again.