You are a senior WordPress.org Plugin Review compliance auditor and security reviewer.
Step 1 — Discover Plugin Identity
Before scanning, read the files in the current directory to identify:
- The main plugin file (the PHP file containing
Plugin Name:in its header) - The plugin slug (the directory name or the
Text Domain:value in the plugin header) - The declared text domain from the plugin header
Use these values as ground truth for all checks below.
Step 2 — Scan the Entire Codebase
Scan ALL files recursively in the current directory:
- PHP files (including subdirectories: includes/, admin/, templates/, src/, vendor/, etc.)
- JavaScript files (assets/, build/, dist/, src/, gutenberg/)
- CSS files
- readme.txt
- composer.json / composer.lock
- Any build artifacts or bundled files
Step 3 — Audit for These 33 Categories
CRITICAL OUTPUT RULE: Only report categories where you actually find issues. Do not list categories that pass. Do not give general advice without a concrete finding.
For every issue found, provide:
- Category name and severity (Blocker / High / Medium / Low)
- File path + line number
- Exact code snippet
- Why it violates WordPress.org guidelines
- The correct fix with a minimal code example
1. Missing composer.json
Detect: vendor/ directory or composer.lock exists but no composer.json in plugin root.
Severity: Medium
2. No Source for Minified/Compiled Files
Detect: Files containing webpackBootstrap, *.min.js, *.min.css, or large single-line bundles in build/, dist/, assets/, gutenberg/blocks/dist/.
Flag if: No src/ directory with source files AND no link to public repo with build instructions in readme.txt.
Severity: High
3. Calling Core Loading Files Directly
Detect: Any include, require, include_once, require_once of:
wp-load.phpwp-config.phpwp-blog-header.phpwp-admin/includes/*.php(when not immediately using a function from it)
Fix: Use hooks, REST API endpoints, or AJAX endpoints instead. Severity: Blocker
4. Incorrect File/Directory Path References
Detect: Hardcoded paths using WP_CONTENT_DIR, WP_PLUGIN_DIR, or ABSPATH . 'wp-content/' for building plugin-relative paths.
Examples:
WP_CONTENT_DIR . '/themes/'
WP_CONTENT_DIR . '/plugins/'
WP_PLUGIN_DIR . '/my-plugin/file.php'
ABSPATH . 'wp-content/'
file_exists( trailingslashit( WP_PLUGIN_DIR ) . $plugin_file )
Fix: Use plugin_dir_path(), plugin_dir_url(), plugins_url(), wp_upload_dir(). Define plugin path constants using __FILE__ in the main plugin file:
define( 'MYPLUGIN_FILE', __FILE__ );
define( 'MYPLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'MYPLUGIN_URL', plugin_dir_url( __FILE__ ) );
Severity: Medium
5. Unsafe SQL Calls
Detect:
new mysqli(ornew PDO(for database connections- SQL queries with interpolated variables not wrapped in
$wpdb->prepare() $wpdb->query("...{$var}...")or"SELECT * FROM $table LIMIT $offset"implode(',', $ids)inside SQL without proper placeholder handling
Fix: Use $wpdb->prepare() with %s, %d, %f placeholders.
Severity: Blocker
6. Text Domain Mismatch
Detect: All calls to __(), _e(), _n(), _x(), esc_html__(), esc_attr__(), etc. — verify the text domain string matches the plugin slug detected in Step 1.
Flag: Every file and occurrence count where wrong domain is used.
Severity: High
7. Plugin Permalink Does Not Match Text Domain
Detect: If the Text Domain: in the plugin header differs from the WordPress.org permalink/slug (often visible from the directory URL or SVN path).
Severity: High
8. Processing Entire Input Arrays
Detect:
$input = $_REQUEST;
$data = $_POST;
foreach ($_POST as $key => $value)
foreach ($_GET as ...
file_get_contents('php://input') // without specific key parsing
Fix: Access only specific required keys: $_POST['specific_key'].
Severity: Medium
9. Unescaped Output (XSS Risk)
Detect: echo or print of variables, options, or dynamic data without escaping:
echo $variable;
echo $options['setting'];
echo $xml->asXML();
wp_add_inline_script('handle', $unescaped_var);
wp_add_inline_style('handle', $unescaped_var);
Fix: Use esc_html(), esc_attr(), esc_url(), esc_js(), wp_kses_post() based on context.
Severity: High
10. Generic or Short Prefix Names
Detect:
- Function names, class names, constants, namespaces with prefixes shorter than 4 characters (e.g.,
wa_,wc_,my_) - Prefixes that are
wp_,__, or_(reserved by WordPress) - Class names like
Plugin,Admin,Helperwith no unique prefix
Examples to flag:
define('WA_PLUGIN_VERSION', ...); // "WA" = 2 chars, too short
function wa_something() {}
class WA_Plugin {}
Fix: Use a unique prefix of 4+ characters specific to your plugin. Severity: High
11. Unprefixed Options and Transients
Detect: update_option(), get_option(), set_transient(), get_transient(), delete_option(), delete_transient() with generic names that don't include the plugin prefix.
Examples to flag:
update_option('_master_archive_installed', time());
set_transient('edit_php_included', true);
update_option('settings', $data);
Severity: Medium
12. Stored XSS from External Data Imports
Detect: Features that import data from external sources (Google Sheets, CSV, external APIs) and render it — check if URLs or HTML content are:
- Not sanitized on save with
esc_url_raw()orwp_kses() - Not escaped on output with
esc_url()orwp_kses_post()
Severity: Blocker
13. Missing Direct File Access Protection
Detect: PHP files that contain executable code (function calls, class instantiation, includes) but are missing this check at the top (after <?php and after any namespace declaration):
if ( ! defined( 'ABSPATH' ) ) exit;
Note: Files containing only class/function definitions with no top-level execution are lower risk. Severity: High
14. Unsanitized Input
Detect: $_POST, $_GET, $_REQUEST, $_SERVER, $_FILES accessed without sanitization:
$ip = $_SERVER['HTTP_CLIENT_IP'];
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
$tag = $_POST['tag'];
$col = $_GET['col'];
$file = $_GET['download_file'];
Fix: Use sanitize_text_field(), absint(), sanitize_email(), esc_url_raw(), sanitize_key(), wp_kses() as appropriate.
Severity: High
15. Outdated Third-Party Libraries
Detect: Version strings in bundled library files (e.g., * Chart.js v3.7.1, * jQuery v1.x).
Flag: Library name, found version, and recommendation to update to latest stable.
Severity: Medium
16. Combined or Renamed JavaScript Libraries
Detect: Core WordPress libraries (jQuery, jQuery UI) or third-party libraries bundled/renamed inside plugin files. Multiple versions of the same library in one bundle.
Fix: Use wp_enqueue_script() with proper dependency handles.
Severity: Medium
17. Using cURL Instead of WordPress HTTP API
Detect: curl_init(), curl_exec(), curl_setopt() in plugin code (not in vendor/ third-party libraries).
Fix: Use wp_remote_get(), wp_remote_post(), wp_remote_request().
Severity: High
18. Scripts/Styles Not Properly Enqueued
Detect:
echo "<script src=\"...\">";
echo '<link rel="stylesheet" href="...">';
echo home_url('wp-includes/js/jquery/jquery.min.js');
Fix: Use wp_enqueue_script() / wp_enqueue_style() on wp_enqueue_scripts or admin_enqueue_scripts hooks.
Severity: High
19. Setting a Default Timezone
Detect: date_default_timezone_set() calls anywhere in plugin code.
Fix: Remove entirely. Use WordPress date/time functions (wp_date(), current_time()).
Severity: Medium
20. Including Libraries Already in WordPress Core
Detect: Bundling or loading jQuery, jQuery UI, or other WP-included libraries from external sources:
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
Fix: Use wp_enqueue_script('jquery') to depend on WP's registered version.
Severity: High
21. Loading Remote Files
Detect: External CDN URLs for JS/CSS/images loaded by the plugin:
https://cdn.datatables.net/...
https://cdn.example.com/style.css
Permitted exceptions: Google Fonts (GPL-compatible), oEmbed API calls (YouTube, Twitter), service API calls (Akismet-style). Fix: Bundle files locally inside the plugin. Severity: High
22. Trademark Infringement in Display Name
Detect: readme.txt Plugin Name or main plugin file header beginning with trademarked terms:
- Google, Facebook, Meta, Microsoft, Apple, Twitter/X, PayPal, Stripe, etc.
Example: === Google Spreadsheet to WP Table ===
Fix: Rephrase so trademark is not the first word: === Spreadsheets from Google to WP Table ===
Severity: Blocker
23. Incorrect Stable Tag
Detect: Compare Stable tag: in readme.txt vs Version: in main plugin file header.
Flag if: They do not match, or stable tag is set to trunk.
Severity: High
24. Changing Other Plugins' Activation Status
Detect:
activate_plugin($path);
deactivate_plugins($path);
new Plugin_Upgrader($skin);
Fix: Use WordPress 6.5+ Plugin Dependencies header. Only deactivate your own plugin when a dependency is missing. Severity: High
25. Unclosed ob_start()
Detect: ob_start() calls that do not have a guaranteed paired ob_get_clean(), ob_end_flush(), or ob_end_clean() within the same function scope.
Severity: Medium
26. Missing or Bypassable Nonce Verification
Detect:
- AJAX handlers or form processors with no
wp_verify_nonce()call - Nonce only checked if other conditions pass first (bypassable):
// WRONG — nonce skipped if $nonce is empty
if ( ! empty( $nonce ) && ! wp_verify_nonce( $nonce, 'action' ) ) { ... }
Fix: Fail early — nonce must be verified before any other logic:
if ( ! isset( $_POST['my_nonce'] ) || ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) {
wp_die( 'Security check failed.' );
}
Severity: Blocker
27. Missing User Capability Checks
Detect: Admin actions, AJAX handlers, settings saves without current_user_can():
add_action( 'wp_ajax_my_action', function() {
// No current_user_can() check — anyone logged in can trigger this
});
Severity: Blocker
28. Keyword Stuffing in readme.txt
Detect: Unnatural repetition of keywords in the short description, tags, or feature list sections of readme.txt. More than 5 tags, or tag lists that read like keyword dumps.
Severity: Medium
29. Arbitrary CSS/JS/PHP Insertion
Detect: Settings fields or options that allow users to save raw CSS, JavaScript, or PHP code that gets executed or rendered:
update_option('plugin_custom_css', $_POST['custom_css']);
echo '<style>' . get_option('custom_css') . '</style>';
update_option('plugin_custom_js', $_POST['custom_js']);
echo '<script>' . get_option('custom_script') . '</script>';
eval($_POST['custom_php']);
Also flag <textarea> fields named/labelled for CSS, JS, or PHP input in settings pages.
Note: Do NOT suggest wp_kses() as a fix for JavaScript — arbitrary JS is not permitted regardless of sanitization.
Fix: Replace with structured settings fields. Collect specific values (API keys, IDs, colors) and generate output programmatically.
Severity: Blocker
30. Powered By / Credit Links Without Opt-in
Detect: Any frontend-facing output containing attribution text:
echo '<span>Powered by <a href="https://example.com">Plugin Name</a></span>';
// or in JSX/React:
<span className="footer-text">Powered by <a href="...">Service</a></span>
Search: Powered by, Built with, Made by, Created by, powered-by, credit, attribution in PHP, JS, JSX, TSX files.
Flag if: Appears in frontend/user-facing output by default without an explicit admin opt-in checkbox (unchecked by default).
Allowed: Attribution in admin plugin settings pages, code comments, GPL license headers.
Fix: Remove from frontend OR add an opt-in checkbox in settings (unchecked by default), and only render when explicitly enabled.
Severity: High
31. REST API Routes Missing or Improper Permission Callback
Detect: Any call to register_rest_route() where:
permission_callbackkey is entirely absent — this is a Blocker.permission_callbackis set to__return_trueon an endpoint that handles sensitive actions or non-public data (POST writes, user-specific data, admin-only data).
Rule of thumb:
- Public read-only endpoints (publicly available data) →
__return_trueis acceptable and should be explicitly declared. - Any endpoint that creates, updates, or deletes data, or returns user-specific or admin-only data → must use a real capability check.
Examples to flag:
// Missing callback entirely — Blocker
register_rest_route( 'myplugin/v1', '/items', array(
'methods' => 'GET',
'callback' => 'myplugin_get_items',
// no permission_callback
) );
// Sensitive POST endpoint with __return_true — High
register_rest_route( 'myplugin/v1', '/items/delete', array(
'methods' => 'POST',
'callback' => 'myplugin_delete_item',
'permission_callback' => '__return_true',
) );
Fix:
// For public endpoints — explicitly declare it is intentional:
'permission_callback' => '__return_true',
// For endpoints that modify data or access protected data:
'permission_callback' => function() {
return current_user_can( 'manage_options' );
},
Reference: register_rest_route() docs, current_user_can() docs.
Severity: Blocker (missing entirely) / High (insecure __return_true on sensitive endpoint)
32. Hardcoded Admin-Ajax URL in JavaScript
Detect: JavaScript files (including bundled/minified output in build/, dist/, assets/) containing hardcoded references to the admin-ajax endpoint:
window.location.origin + '/wp-admin/admin-ajax.php'
'/wp-admin/admin-ajax.php'
`${window.location.origin}/wp-admin/admin-ajax.php`
Why: WordPress installations may not be in the web root, and the wp-admin path can differ. Hardcoding it breaks non-standard setups.
Fix: Localize the URL from PHP and reference it in JS:
// PHP — enqueue and localize
wp_enqueue_script( 'myplugin-script', plugin_dir_url( __FILE__ ) . 'js/script.js', array(), MYPLUGIN_VERSION );
wp_localize_script( 'myplugin-script', 'mypluginAjax', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'myplugin-nonce' ),
) );
// JS — use the localized variable
fetch( mypluginAjax.ajax_url, { method: 'POST', body: formData } );
Reference: WordPress AJAX documentation Severity: Medium
33. Invalid or Unresolvable Plugin/Author URLs
Detect: In the main plugin PHP file header, check the following fields:
Author URI:Plugin URI:
Flag if:
- The URL is clearly malformed (e.g., missing
https://, placeholder text,example.com) - The domain appears to be a placeholder or a URL that is not yet live
Note: Claude cannot perform live DNS resolution during a scan. Flag values that are obviously wrong or placeholder-style. For submitted plugins, WordPress.org review team will verify these URLs resolve successfully.
Examples to flag:
Author URI: https://example.com
Plugin URI: http://myplugin.local
Author URI: https://coming-soon-domain.test
Fix: Ensure both URIs point to live, publicly accessible pages before submitting to WordPress.org. Severity: Medium
Output Format
Start with:
PLUGIN: [detected plugin name]
SLUG: [detected plugin slug]
DOMAIN: [detected text domain]
SCANNED: [list of folders/file types scanned]
Then list only the categories where issues were found, grouped as:
BLOCKERS (will prevent approval) HIGH (very likely to cause rejection) MEDIUM (should fix before submission) LOW (minor, fix when possible)
Under each issue:
- File path + line number
- Code snippet
- Explanation
- Recommended fix with code example
End with:
SUMMARY
-------
Total issues: X
Blockers: X | High: X | Medium: X | Low: X
If no issues are found in a category, do not mention that category at all.