Skip to content

Security: runtypelabs/persona-wordpress-plugin

Security

docs/security.md

Security notes

  • The rt_ API key is a server-side secret, never localized to the browser, used only in wp_remote_* calls. There is no API-key form field; supply it via the PERSONA_ASSISTANT_API_KEY wp-config constant (a legacy stored key is still honored), scoped to CLIENT_TOKENS:WRITE + AGENTS:READ + PRODUCTS:SURFACES:READ.

  • The ct_ client token is browser-safe (scoped to your origin and bound to your assistant's chat surface, rate-limited server-side).

  • Provider API keys are never touched; built-in-AI calls stay server-side via Persona_Assistant_AI.

  • The WP-AI REST endpoint always has a permission_callback. A "require login to chat" setting gates access, and a per-visitor, per-minute rate limit (default 10, filterable via persona_assistant_wp_ai_rate_limit, hard-capped 1-60) throttles usage; the persona_assistant_rest_permission filter remains available for stricter site-specific policy.

  • The WordPress AI Client gates generation behind the prompt_ai capability (admins only by default). For a public widget, Persona_Assistant_AI grants prompt_ai only for the duration of the trusted server-side call, while the audience/rate-limit settings and persona_assistant_rest_permission filter remain the cost and abuse controls.

  • Attachments are decoded and checked against the administrator's MIME/count/size policy before reaching a connector. WordPress AI Abilities are intersected with an explicit admin allowlist and must be marked read-only and non-destructive; their native permission callbacks still execute.

  • WordPress AI requests include WordPress's REST nonce so cookie authentication, account-history ownership, and Ability permission callbacks see the actual visitor.

  • Persona uses a plugin-supplied storage adapter. It persists only user/assistant text and strips attachments, inline file data, reasoning, tool activity, artifacts, and metadata before writing to session or device storage.

  • Optional WordPress AI account history uses dedicated owner-scoped tables: {prefix}persona_assistant_conversations (public UUID, owning user ID, first-message title, timestamps, retention deadline) and {prefix}persona_assistant_messages (user/assistant text only, capped at the most recent 40 messages per conversation — the same stripping policy as the browser adapter applies before writing). It is disabled by default, full-screen Assistant Page only, retention-controlled (daily cron cleanup of expired rows, immediate deletion with the owning WordPress user), and registered with WordPress personal-data export and erasure. Every REST route and the chat endpoint's conversationId parameter revalidate ownership against the logged-in user. uninstall.php drops both tables permanently on plugin deletion; deactivation preserves them.

  • Runtype remains canonical for Runtype transcripts; WordPress stores no duplicate transcript and remembers only an opaque session ID for the selected browser lifetime.

  • Verified WordPress identity in Runtype mode is opt-in. wp:<ID> is the default stable subject; email is shared by default when identity is enabled, with a separate administrator opt-out. Neither applies to anonymous visitors or WordPress AI.

  • Identity tokens require cookie-authenticated WordPress login plus an explicit wp_rest nonce header, and have a per-user rate limit (10/minute, filterable, capped at 1–60). They cannot be requested for another user by supplying a request-body ID. Required JWT claims cannot be overridden through the claims filter, and email opt-out is enforced after filtering.

  • RSA 2048 private keys live in non-autoloaded WordPress options. Keep database backups protected. Public JWKS responses expose only RSA public parameters and use a 60-second cache; token responses prohibit caching. Browser proof caching uses tab memory only and expires before the five-minute JWT lifetime.

  • Signing key rotation publishes the new key two minutes before switching signatures and retains the previous public key for fifteen more minutes. HTTPS/public-address checks prevent enabling identity for local/private/HTTP issuers; actual DNS reachability and signature admission are verified by Runtype.

  • The browser rejects missing/rejected identity proofs for verified conversations. Visitors may explicitly start a new, ephemeral browser-only chat; account-bound credentials/transcripts are never silently reused or replayed without a proof. Account-enabled persistence is partitioned by WordPress user to prevent shared-device account switches from inheriting local transcripts or visitor credentials.

  • Prompt preparation, integration ID saves, setup verification, and key rotation require manage_options plus an admin nonce. Registration happens through an external MCP/CLI/API connection; its management credentials are not sent to WordPress and the existing WordPress OAuth grant is not expanded. The setup prompt contains public configuration only. Verification uses an administrator’s short-lived proof and the origin-bound browser client token, accepts only an admitted identity, and discards proof/session credentials. The identity integration and Runtype organization feature flags must be configured before attribution works. Uninstall always deletes signing material locally and attempts remote integration deletion with the available server credential.

There aren't any published security advisories