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_unflag_comment check_admin_referer replymind_unflag_comment_{$comment_id}
handle_clear_logs check_admin_referer replymind_clear_logs
handle_dismiss_onboarding check_admin_referer replymind_dismiss_onboarding
handle_remove_api_key (legacy) 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
Reply Rules AJAX (save / delete / toggle / reorder) check_ajax_referer replymind_pro_rules_nonce
Per-post meta box save wp_verify_nonce replymind_pro_post_overrides_{$post_id}
Multisite push settings POST nonce admin_post_replymind_pro_push_settings
Email approval Approve / Reject Single-use transient token, 48 h

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, rules and mappings, batch operations, clear logs, analytics 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. No API keys in the plugin

Since 1.1.0 ReplyMind stores no provider credentials whatsoever. All AI requests go through the AI Client built into WordPress 7.0 (wp_ai_client_prompt()):

  • Keys live in WordPress core, under Settings → Connectors, protected by core's own storage and capability rules. ReplyMind never reads, renders, or transmits them.
  • The plugin makes no outbound HTTP calls of its own. It hands a prompt to the AI Client and consumes the returned text — nothing more.
  • Nothing is ever sent to approidtech.com. There is no licensing server, no telemetry, no update channel outside WordPress.org.
  • A legacy replymind_api_key option and its sanitizer are retained only for backward compatibility; the field is hidden whenever the AI Client is active, and the option is deleted on uninstall.

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, Anthropic Claude, or Google Gemini), via the WordPress AI Client. Sentiment scoring happens locally first.
Post title + 40-word excerpt + author bio The same AI provider
Provider credentials Stored by WordPress core (Settings → Connectors), never touched by ReplyMind
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 talks only to the WordPress AI Client, which talks to the provider you chose.


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