Agent2Wp
Security engine for WordPress AI Abilities — fail-closed risk classification, permission gating, audit logging, and MCP/REST execution controls.
by Taibur Rahaman · github.com/taibur-rahaman/agent2wp · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/taibur-rahaman/agent2wp/archive/refs/heads/main.zipReadme
Agent2Wp
Security-focused AI agent infrastructure for WordPress, built around the WordPress Abilities API and the Model Context Protocol (MCP).
Most WordPress AI plugins generate text. Agent2Wp is concerned with what happens when an AI agent executes something — and with making that execution authorized, classified, rate-limited, audited, and fail-closed.
Developed by Taibur Rahaman.
Why Agent2Wp?
AI agents connected over MCP can already perform meaningful WordPress operations: writing files, running WP-CLI, executing PHP, editing content. The hard part is not capability. It is answering, for every single invocation:
- What is being invoked?
- How dangerous is it?
- Is the caller allowed to do it?
- What happened, and can anyone tell afterwards?
Agent2Wp is the engine that answers those questions. Three design commitments shape it:
It is WordPress-native. Authorization lives at the WordPress ability invocation boundary — WP_Ability::check_permissions() — not at a single HTTP endpoint. WordPress can invoke an ability over MCP, over the REST route /wp-abilities/v1/abilities/{name}/run, or internally from PHP. A gate that only watches one of those is not a gate.
It fails closed. An unknown ability, an unresolvable path, a malformed name, or a classification fault all produce a denial classified as CRITICAL. There is no permissive default anywhere in the risk registry.
It does not pretend. Some execution paths genuinely sit outside the gate. They are documented below, tested, and audited rather than quietly omitted.
Key Features
Implemented and covered by the test suite:
| Area | Capability |
|---|---|
| Execution Gate | Central authorization at the WordPress ability boundary, reached from MCP, REST, and internal execution |
| Ability identity resolution | Recovers the canonical WP_Ability name so decisions can be per-action; fails closed when identity is uncertain |
| Risk classification | Every registered ability classified LOW/MEDIUM/HIGH/CRITICAL, with no default tier |
| Path-sensitive escalation | File abilities become CRITICAL when the resolved target can affect PHP execution |
| Sandbox awareness | .php, .htaccess, .user.ini, php.ini, .php.ini, web.config, and .disabled-suffixed equivalents recognised as privilege-establishing |
| Identity | MCP session identity where verifiable, WordPress-user identity otherwise, unknown when neither — never fabricated |
| Audit log | Every decision recorded: action, risk, outcome, identity, boundary, correlation id |
| Audit redaction | Allowlist-generated summaries; no raw request payload column exists in the schema |
| Session-reference hashing | Session identifiers stored as truncated SHA-256, never as replayable values |
| Rate limiting | Per-user sliding window on high-risk tiers; a resource control, never an authorization mechanism |
| Developer Mode | The existing WordPress authorization model, now running through the Gate with classification and audit |
| Boundary auditing | Gutenberg finalizer and token-redemption paths recorded as explicitly out-of-boundary |
| Test suite + CI | 233 tests, 719 assertions; formatting and tests blocking in CI |
Security Model
Every ability invocation resolves through the same decision sequence:
Agent request
↓
Resolver self-test (safe mode if identity cannot be established)
↓
Agent2Wp enabled state
↓
Ability identity resolution
↓
Risk classification (unknown → CRITICAL → deny)
↓
WordPress safety constants (DISALLOW_FILE_EDIT / DISALLOW_FILE_MODS)
↓
WordPress capability check
↓
Mode (Developer / Managed)
↓
Policy boundary (Stage 4 — not yet implemented)
↓
Rate limiting
↓
Audit intent
↓
WordPress Ability executes
Decisions are ordered by authority: system safety → business policy → user instruction → agent request. A lower layer can only ever narrow a decision. Nothing an agent supplies — input fields, a dry_run flag, a claimed mode or risk level — participates in authorization.
The claim this project makes, precisely
All Agent2Wp ability invocation paths are designed to resolve through the Gate; specific legacy and human-oriented execution paths remain outside the Gate and are explicitly documented and tested.
That wording is deliberate. "Everything is protected" would be false.
Important Security Boundary
Inside the Gate
- MCP ability execution
- REST ability execution —
POST /wp-abilities/v1/abilities/{name}/run - Internal
$ability->execute()
All three converge on WP_Ability::check_permissions() → agent2wp_permission_callback() → the Gate. The REST path is explicitly tested against execute-php, run-wp-cli, delete-file, and delete-post so a future bypass fails the suite.
Outside the Gate
| Path | Why | What protects it |
|---|---|---|
Persisted PHP loaded by sandbox-loader.php |
The Gate authorizes the write, not every later request that loads the file | Writing it is classified CRITICAL; the sandbox directory is confined; a crash marker disables loading |
| Gutenberg finalizer REST mutation | Driven by a human in a browser tab and authorized on edit_posts; gating it on manage_options would break the editors it exists for |
Lease validation, base-content hashing, capability checks; audited as out-of-boundary |
| Upload-link redemption | Authenticated by its own HMAC-signed token, which is the endpoint's authentication mechanism | HMAC signature, expiry, path confinement, symlink rejection; audited |
| Admin-access-link exchange/login | Authenticated by a one-time token plus binding nonce | One-time token, binding nonce, expiry, administrator-only issuance; audited |
The limitation that matters most
Persisted PHP inside the Agent2Wp sandbox executes on subsequent WordPress requests independently of the Gate. Disabling Agent2Wp does not automatically stop already-persisted sandbox PHP.
A single gated write can establish code that then runs on every request, outside the Gate, indefinitely. This is why writing PHP-execution-affecting files is classified CRITICAL, and why removing such code requires deleting the file or using the sandbox safe-mode marker. This is an architectural property of letting agents persist PHP, not an oversight.
Risk Classification
| Tier | Meaning |
|---|---|
| LOW | Read-only or low-impact operations |
| MEDIUM | Controlled content modification and staged changes |
| HIGH | Significant content or operational changes |
| CRITICAL | Operations capable of establishing or exercising privileged execution |
Unconditionally CRITICAL: execute-php, run-wp-cli, delete-file, delete-post, create-admin-access-link.
Path-sensitive escalation: write-file, edit-file, and enable-file are HIGH for inert targets and become CRITICAL when the resolved target is PHP-execution-affecting. A trailing .disabled is stripped before classification, because foo.php.disabled can be renamed back into an executing foo.php.
Classification calls the same path resolver the abilities themselves call, rather than implementing a second normalization system — so the classifier and the executing code can never disagree about which path is being addressed. An unresolvable path is CRITICAL, never a lower tier.
Current Project Status
Implemented
- Stage 0 — Testing infrastructure and CI
- Stage 1 — Registry and risk classification
- Stage 2 — Identity and audit
- Stage 3 — Execution Gate and Developer Mode
Planned — not yet implemented
- Stage 4 — Policy engine and approval workflow
- Stage 5 — Dry-run, verification, and rollback
- Stage 6 — Audit review before any Managed Mode default
Managed Mode infrastructure exists so the Gate has a real decision point, but the policy and approval engines it depends on arrive in Stage 4. Until then Managed Mode denies high-risk and critical actions rather than fabricating an approved result. Managed Mode is not production-ready and is not the default. Developer Mode is the default on every installation.
Testing
233 tests, 719 assertions
Coverage includes path classification, directory traversal, percent-encoded traversal, symlinked parent directories, .disabled targets, PHP-execution-affecting configuration files, unresolvable paths, identity resolution across all three sources, audit redaction, audit write-failure behavior, installation idempotency, invocation paths, REST bypass attempts, MCP dispatch, internal execution, Gate decisions, rate limiting, and mode handling.
Scope of that evidence. The Gate and resolver tests run against a faithful WP_Ability double that reproduces the call path documented in the WordPress stubs. They prove the mechanism and the fail-closed behavior. Live validation of the stack-based ability resolver against a real WordPress 6.9 installation remains a pre-release requirement and has not yet been performed.
Architecture
flowchart TD
A[AI Agent / MCP Client] --> B[WordPress Ability Execution]
R[REST /wp-abilities/v1/.../run] --> B
I[Internal execute] --> B
B --> C[Agent2Wp Resolver]
C --> D[Risk Registry]
D --> E[Execution Gate]
E --> F[Mode / Capability / Rate Limit]
E --> G[Audit Intent]
E --> H[WordPress Ability]
H --> J[Audit Outcome]
Paths that do not traverse the Gate, recorded as out-of-boundary:
flowchart TD
S[sandbox-loader.php] --> S1[Persisted PHP executes each request]
GB[Gutenberg finalizer REST] --> GB1[Content mutation]
UL[upload-link redemption] --> UL1[File write]
AL[admin-access-link] --> AL1[wp-admin session]
S1 --> AU[Audit only]
GB1 --> AU
UL1 --> AU
AL1 --> AU
Installation
Agent2Wp is distributed as a WordPress plugin ZIP built from this repository.
- Download the latest
agent2wp-x.x.x.zipfrom Releases. - In WordPress: Plugins → Add New → Upload Plugin, choose the ZIP, install, activate.
- Open Agent2Wp → Start and click Start Agent2Wp.
- Copy the generated MCP configuration into your AI client.
The GitHub Code → Download ZIP archive will not work: it does not include the bundled
vendor/directory that provides the MCP Adapter. Always use the Releases ZIP.
To build the installable ZIP from a checkout:
composer install
./build/build 2.0.0
Requirements: WordPress 6.9+ (Abilities API), PHP 8.0+, and a staging or development environment. See Security Model before considering any other environment.
Development
composer install
composer test
Quality tooling (Mago):
make mago-format # auto-format, print-width 120
make mago-lint # lint
make mago-analyze # static analysis
make mago-check # all three
CI runs formatting and the PHPUnit suite as blocking checks. Lint and static analysis run as non-blocking reports while pre-existing debt is cleared — the current counts are recorded in .github/workflows/ci.yml and in CLAUDE.md so that new issues are distinguishable from old ones.
Security Disclosure
Please report security issues privately through GitHub Security Advisories rather than opening a public issue. See SECURITY.md.
If your report concerns one of the documented out-of-boundary paths above, please say so — those are known and intentionally scoped, and it helps to separate them from new findings.
Roadmap
| Stage | Scope | Status |
|---|---|---|
| 0 | Testing infrastructure, CI | Implemented |
| 1 | Registry, risk classification | Implemented |
| 2 | Identity, audit log | Implemented |
| 3 | Execution Gate, Developer Mode | Implemented |
| 4 | Policy engine, approval workflow | Planned |
| 5 | Dry-run, verification, rollback | Planned |
| 6 | Audit review before Managed default | Planned |
Project Philosophy
- WordPress-native. Authorization sits at the ability boundary WordPress actually invokes, not at a convenient HTTP chokepoint.
- Fail closed. Uncertainty produces denial, never a permissive default.
- Explicit boundaries. Paths outside the Gate are named, documented, and tested rather than omitted.
- Auditable execution. Every decision — allow and deny alike — is recorded.
- No security theater. The audit log stores no raw payloads, so it is not worth stealing; claims are scoped to what tests actually demonstrate.
- Minimal disruption. The Gate was added without rewriting any of the 33 existing ability registrations.