TL;DR


1. Nonces verified before any other request data is read

Every handler that changes state verifies a WordPress nonce as the very first statement in the function body. No $_GET, $_POST, or $_REQUEST data is read before nonce verification — including the comment ID. Static analysers (WordPress.org Plugin Check) confirm this.

Handler Verification helper Nonce action
handle_generate_reply check_admin_referer replymind_generate_reply
handle_publish_reply check_admin_referer replymind_publish_reply
handle_clear_logs check_admin_referer replymind_clear_logs
handle_remove_api_key check_admin_referer replymind_remove_api_key
handle_submit_reply (AJAX) check_ajax_referer replymind_submit_reply
handle_batch_count (AJAX) check_ajax_referer replymind_batch_nonce
handle_batch_process (AJAX) check_ajax_referer replymind_batch_nonce
handle_batch_publish (AJAX) check_ajax_referer replymind_batch_nonce

If a nonce check fails, WordPress halts the request immediately with a 403 — no error message that could leak information, no partial side effect.


2. Capability gates

Every action requires the user to have the right WordPress capability:

Operation Capability Default roles that have it
Generate / publish / submit a reply on a comment edit_comment (per-comment) Administrator, Editor
Save settings, batch operations, clear logs, remove API key manage_options Administrator only

Capability is checked after the nonce — so a stolen nonce by itself can't trigger anything; a stolen nonce + a privileged user is the only path. WordPress enforces both.


3. No notice state in URL query args

Many WordPress plugins pass admin-notice text through URL parameters after a redirect. That's a common XSS vector if not perfectly sanitized. ReplyMind doesn't do this.

After every state-changing action, the plugin stores a notice in a per-user transient:

replymind_notice_{user_id} → { type, message }     (60-second TTL)

admin_notices() reads the transient (which is internal data, not user input), renders the message escaped, and deletes the transient. No URL query arg is read for notice content. Nothing the renderer touches is unverified user input.


4. API key handling

The API key is stored in WordPress as the option replymind_api_key, but the way it's surfaced and handled differs from how most plugins do it:

  • The live key is never written into the page DOM. The settings field renders an empty value attribute. Even an admin opening View Source won't find their key in the HTML.
  • Saving with the field empty keeps the existing key. A custom Settings API sanitizer treats an empty submission as "leave the saved value alone." So a normal "Save Changes" click can never accidentally wipe the key.
  • Removing the key is a separate, nonce-protected action. A dedicated Remove key button next to the field calls a handler that deletes the option after verifying its own nonce.
  • The key is readable from the database by users with the manage_options capability — that's standard WordPress option behaviour and we document it openly. No false claims of encryption-at-rest.

5. Prompt-injection guard

A common attack against AI-driven plugins is for visitors to embed instructions in their comment, like:

ReplyMind's system prompt explicitly instructs the model:

This significantly reduces successful prompt-injection rates. The guard is unconditional — it's applied to every single generation, regardless of mode or model.


6. Comment HTML stripping

Before the comment text is sent to the AI, it's run through:

  1. wp_strip_all_tags() — removes all HTML and PHP tags, including their content.
  2. html_entity_decode() — converts &, <, >, etc. back to plain characters so the model sees readable text without escaped sequences.

Embedded <script> tags, malformed HTML, or attempts to inject Markdown that looks like instructions ("# IMPORTANT INSTRUCTION:") are all neutralised before the model sees them.


7. API error body truncation

When the AI provider returns a non-200 response, the body could in principle contain anything — including echoed-back parts of the request. ReplyMind:

  1. Tries to parse the body as JSON and extract error.message (the canonical field).
  2. Falls back to the raw body run through wp_strip_all_tags.
  3. Caps the resulting string at 300 characters before logging or displaying.

This prevents pathological provider responses from inflating log entries or surfacing more detail than necessary in admin notices.


8. Daily rate limit

A misconfigured auto-mode site, a runaway batch run, or a malicious approval flow could in theory burn through a lot of API credits. ReplyMind caps it:

  • Default cap: 100 successful API calls per day, per site.
  • Counter: date-keyed transient (replymind_daily_YYYYMMDD); resets daily at server-local midnight.
  • Counts only successful calls — failed attempts don't consume quota.
  • Filterable: add_filter( 'replymind_daily_limit', fn() => 500 ). Set to 0 to disable entirely.
  • Batch operations respect the cap. When the cap is hit mid-batch, the loop stops cleanly and the remaining comments stay unmarked for tomorrow's run.

9. Asset enqueuing — no inline scripts or styles

Every CSS and JS file is enqueued via wp_enqueue_style / wp_enqueue_script on the admin_enqueue_scripts hook. There are zero inline <script> or <style> blocks anywhere in the plugin's PHP. This means:

  • Your Content Security Policy (CSP) doesn't need to allow inline scripts for ReplyMind.
  • Caching plugins can correctly version and cache the assets.
  • Translation tooling (wp_localize_script) is used for dynamic data.

10. WordPress.org review process

The plugin is hosted on the WordPress.org plugin directory. Every release:

  • Is statically analysed by Plugin Check, the official linting tool.
  • Follows the Plugin Directory Guidelines.
  • Is human-reviewed on initial submission.

The plugin's full source is openly auditable under the GPL v2 (or later) licence.


11. What about my data?

Data Where it goes
Comment text The AI provider you configure (OpenAI or Anthropic)
Post title + 40-word excerpt + author bio The same AI provider
Your API key Stays in your WordPress database (never sent anywhere except the AI provider's auth header)
Visitor IP, email, name Not sent to the AI — the plugin uses only the comment text and post context
Anything else Doesn't leave your server

Nothing is sent to ReplyMind's servers. We have no servers in the loop. The plugin is a direct client of OpenAI's and Anthropic's APIs.


12. Reporting a security issue

If you've found a vulnerability, don't post it publicly. Email security@approidtech.com with details. We'll respond within 1 business day, fix the issue, and credit you in the changelog if you'd like.


Read next