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
valueattribute. 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_optionscapability — 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:
wp_strip_all_tags()— removes all HTML and PHP tags, including their content.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:
- Tries to parse the body as JSON and extract
error.message(the canonical field). - Falls back to the raw body run through
wp_strip_all_tags. - 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 to0to 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
- How it works — what the plugin actually does
- GDPR compliance — privacy posture in detail
- Developer reference — filters and hooks for advanced use