Security model
This document specifies the security architecture, cryptographic invariants, trust boundaries, and threat mitigation models for SitePilot MCP (0.4.10).
1. Core Trust Boundaries
Section titled “1. Core Trust Boundaries”┌───────────────────────────────────────────────────────────┐│ MCP Client (AI) ││ (Untrusted / Non-Admin) │└─────────────────────────────┬─────────────────────────────┘ │ JSON-RPC (MCP) / REST ▼┌───────────────────────────────────────────────────────────┐│ Client or Optional Orchestration Service ││ (Untrusted Policy Requester) │└─────────────────────────────┬─────────────────────────────┘ │ Authenticated Signed Transport ▼┌───────────────────────────────────────────────────────────┐│ WordPress SitePilot Plugin ││ ★★ Policy Enforcement Point (PEP) ★★ ││ - OAuth 2.1 PKCE S256 Validation ││ - 4-Tier Risk Engine & Scope Guards ││ - Two-Phase Commit State Machine ││ - Fail-Closed Sandboxed Adapters ││ - SHA-256 Change Set & Version Locking │└───────────────────────────────────────────────────────────┘The WordPress plugin is the sole authoritative Policy Enforcement Point (PEP). No external service or client agent can bypass:
- WordPress core capability checks (
current_user_can). - Consented OAuth 2.1 scopes.
- 4-Tier risk policy gates.
- Cryptographic site-version locking (
expected_version). - The dedicated approval capability and channel guard.
2. Authentication & Credential Hygiene
Section titled “2. Authentication & Credential Hygiene”- OAuth 2.1 with PKCE:
- Implements RFC 7636 with mandatory
code_challenge_method = S256. - Exact redirect URI matching (wildcards, fragments, and non-HTTPS URIs are strictly rejected; loopback HTTP is permitted solely for local development).
- Single-use authorization codes with a 5-minute lifespan.
- Implements RFC 7636 with mandatory
- OAuth Discovery and Dynamic Client Registration:
- An unauthenticated request to the canonical
/wp-json/sitepilot-mcp/v2/mcpresource returns aWWW-Authenticatechallenge naming its RFC 9728 protected-resource metadata. The metadata points clients to the authorization server and its RFC 7591 Dynamic Client Registration endpoint. - An RFC 7591 Dynamic Client Registration that omits
scopereceives asite:readregistered ceiling. An explicit registration scope must be a non-empty, supported, space-separated set; SitePilot persists and returns that exact ceiling. - Before consent and token issue, SitePilot intersects an authorization request with the dynamic client’s registered ceiling. A request with no permitted scope fails with
invalid_scope; consent never widens the registration. - Client ID Metadata Document clients remain outside this DCR-specific ceiling because their metadata is retrieved rather than stored as a dynamic registration. Their requested scopes, the WordPress user’s grant authority, and the consent screen remain authoritative.
- Existing dynamic client rows acquire the
site:readceiling during the0.4.10schema upgrade for future authorization requests, but the migration does not narrow already-issued access or refresh tokens. Those grants retain their consented scopes until expiry or revocation. - After upgrading, a site owner must revoke any existing overbroad OAuth connection. A client that legitimately needs broader authority must then register again with an explicit supported
scopeand receive fresh consent.
- An unauthenticated request to the canonical
- Token Security:
- Access tokens expire after 15 minutes.
- Refresh tokens rotate upon use (token family tracking). If a previously used refresh token is presented, the entire token family is immediately revoked to mitigate replay attacks.
- Raw tokens, client secrets, and credentials are never written to logs or audit tables; all tokens are stored as irreversible SHA-256 hashes.
- Claimed Application Passwords:
- SitePilot stores the WordPress Application Password UUID and its immutable scope grant, never the password value.
- An unclaimed Application Password is restricted to the least-privilege fallback; a re-claim cannot silently widen its scopes.
- Zero WordPress Password Exposure:
- SitePilot never requests or stores WordPress account passwords and never returns an Application Password value.
3. Strict Fail-Closed Invariants
Section titled “3. Strict Fail-Closed Invariants”To eliminate entire classes of arbitrary execution vulnerabilities:
| Category | Policy |
|---|---|
| Raw Code Execution | Raw PHP execution, eval, create_function, and arbitrary callbacks are excluded from all tool schemas. |
| Database Access | Direct SQL queries and arbitrary database modifications are prohibited. All data modifications use WordPress/WooCommerce high-level APIs. |
| Filesystem / Shell | Shell execution, exec, proc_open, WP-CLI invocations, and filesystem root operations are not exposed. |
| Configuration | Modification or reading of wp-config.php, .htaccess, or server environment files is blocked. |
| Self-Protection | The active SitePilot MCP plugin, its audit log subsystem, and active recovery snapshot tables cannot be deactivated or deleted via MCP. |
4. Mutation Governance & Approvals
Section titled “4. Mutation Governance & Approvals”- Immutable Change Sets:
- A change set payload is hashed (
SHA-256) upon planning. - An approval is cryptographically bound to
actor_id,site_id,site_version,change_set_hash, and expires in 30 minutes. - Any drift in site state or change set input invalidates the approval, requiring re-inspection and re-planning.
- A change set payload is hashed (
- Controlled Deletion:
- Standard deletions move content to the reversible WordPress Trash.
- Permanent deletion is disabled by default. If enabled by an administrator, each permanent deletion is restricted to an isolated Tier-3 change set requiring explicit, fresh admin approval with no rollback.
- Separated Approval Channel:
- The
/ops/{ability}operation bridge does not registerapprove-change. - Approval requires
sitepilot_approveplus the approval guard. Ordinary OAuth credentials are refused. - Cookie-authenticated wp-admin approval requires a valid nonce. Delegated credential approval is disabled in production and requires both an explicit credential grant and an opt-in constant elsewhere.
- The
5. Distribution boundary
Section titled “5. Distribution boundary”Release-channel configuration, signing operations, storage locations, and publication procedures are intentionally excluded from public documentation.
6. STRIDE Threat Analysis
Section titled “6. STRIDE Threat Analysis”| Threat | Description | SitePilot MCP Mitigation |
|---|---|---|
| Spoofing | Rogue agent posing as authorized client | PKCE S256 OAuth 2.1, hashed tokens, exact credential grants, and token-family revocation. |
| Tampering | Modifying change set inputs post-approval | Approval strictly bound to immutable SHA-256 change set hash; input mutation invalidates approval. |
| Repudiation | Denying an unauthorized site mutation | Database-backed audit trail logging actor, IP, timestamp, diff, and approval IDs. |
| Information Disclosure | Leakage of sensitive site data or secrets | Masked audit logs, nonces/passwords excluded, sandboxed network-disabled DOM parsing. |
| Denial of Service | Resource exhaustion via large HTML/CSS payloads | Bounded payloads (HTML $\le 2\text{ MB}$, CSS $\le 500\text{ KB}$, max 1,500 DOM elements), timeout constraints. |
| Elevation of Privilege | Agent executing admin commands without consent | 4-tier risk engine, scoped credentials, a distinct approval capability, and production approval through the nonce-protected WordPress Admin UI. |
7. Responsible Vulnerability Disclosure
Section titled “7. Responsible Vulnerability Disclosure”If you discover a security vulnerability in SitePilot MCP, please report it privately to the maintainers at security@sitepilot.io or through a private GitHub Security Advisory. Do not open public issues for security vulnerabilities.
Public normalization derived from product commit 00c30de825b68dc14535f6455e82c927ad256b47, file docs/security.md. See source-manifest.json for the source digest and declared normalization classes.