
Here is a scenario that plays out more often than most site owners realize. You install a well-reviewed WordPress plugin to add a contact form or a booking calendar. It works perfectly. What you don't see is that the same plugin quietly writes visitor emails, IP addresses, and form submissions into a database table that any logged-in subscriber can query through an exposed REST endpoint. No hacking required. The data just leaks by design.
According to the WordPress plugin security firm Patchstack, broken access control and information disclosure vulnerabilities made up a large share of the plugin flaws reported in recent years, and a meaningful chunk of those involved data readable by low-privilege users like subscriber or customer. That matters because the default WordPress subscriber role is trivial to obtain: on many sites, anyone can register in seconds. If a plugin trusts "logged in" to mean "trusted," a subscriber account becomes a skeleton key.
In this guide I'll walk you through exactly how to check whether a WordPress plugin leaks data to subscribers, using the same process I run before I trust anything on a client site. You'll get a hands-on testing method, a worked example with real endpoints, a comparison of the tools I actually use, and a checklist you can keep. No fluff.
Key Takeaways
- The WordPress
subscriberrole is often self-service, so treat every subscriber-accessible endpoint as effectively public.- Most data leaks happen through unprotected REST API routes, AJAX handlers missing capability checks, and overly broad database queries.
- The fastest test is to create a real subscriber account, grab its cookies, and hit the plugin's endpoints to see what comes back.
- Look for missing
current_user_can()checks andpermission_callbackset to__return_truein the plugin source.- Automated scanners catch known CVEs, but manual endpoint probing catches the logic flaws scanners miss.
- Pair testing with a defensive layer so that even an undiscovered leak has limited blast radius.
What "leaking data to subscribers" actually means
A data leak to subscribers is any situation where a user with the low-privilege subscriber role (or a similar role like customer in WooCommerce) can read information they were never meant to see. That includes other users' emails, order details, private form entries, admin notes, API keys stored in options, or personal data submitted through the plugin.
The reason this is so dangerous on WordPress specifically: the platform ships with an open registration option, and countless plugins enable it for comments, downloads, or memberships. A subscriber isn't a guest. They're authenticated. Plugins that assume "authenticated equals safe" open a door.
The three technical patterns behind most leaks
- Unprotected REST routes. A plugin registers a route with
permission_callbackset to__return_true, meaning no permission check runs at all. - AJAX handlers without capability checks. The plugin hooks into
wp_ajax_nopriv_orwp_ajax_but never callscurrent_user_can()before returning data. - Insecure direct object references (IDOR). An endpoint accepts an ID like
?entry_id=42and returns that record without verifying the requester owns it.
If you understand these three patterns, you can find the majority of real leaks. Everything below is about triggering them safely on a staging copy.
Set up a safe testing environment first
Never run these tests against a live production site with real customer data. Clone the site to a staging environment. If you host on cPanel or similar, most panels have a one-click staging feature. Otherwise use a local stack like LocalWP or a throwaway subdomain.
- Copy the site (files and database) to
staging.yourdomain.comor a local install. - Install only the plugin you're auditing plus a fresh WordPress core, if you want to isolate its behavior.
- Enable user registration under Settings → General → Anyone can register, with the default role set to
Subscriber. - Create a test subscriber account, for example
tester@example.com. - Install a browser with developer tools (Firefox or Chrome) and, optionally, a request tool like Postman or
curl.
Before you begin poking at anything, it's worth reading our companion guide on how to audit a WordPress plugin for hidden backdoors, because leak testing and backdoor auditing overlap and you'll often want to do both in one sitting.
The hands-on method: test endpoints as a real subscriber
This is the core of the whole process. You're going to log in as your test subscriber, capture the authentication cookies, and then hit every endpoint the plugin exposes to see what data comes back.
Step 1: Enumerate the plugin's REST routes
WordPress publishes a machine-readable list of every registered REST route. Visit this URL in your browser while logged in as the subscriber:
https://staging.yourdomain.com/wp-json/
You'll get a JSON document. Search it (Ctrl+F) for the plugin's namespace, which is usually the plugin slug, for example bookingplugin/v1. Note every route under that namespace. A typical one might look like /wp-json/bookingplugin/v1/entries.
Step 2: Hit each route and inspect the response
Open a route directly, for instance:
https://staging.yourdomain.com/wp-json/bookingplugin/v1/entries
If it returns a 401 or 403 with a "not allowed" message, good. If it returns actual data, you've found a leak. Then repeat while logged in as your subscriber. The critical comparison is: does a subscriber see data an admin would see?
Step 3: Grab your subscriber cookies and script the tests
For thorough testing, use curl with your subscriber session. In your browser's dev tools, open the Network tab, reload the dashboard, click any request, and copy the Cookie header value. Then run:
curl -s "https://staging.yourdomain.com/wp-json/bookingplugin/v1/entries" -H "Cookie: PASTE_YOUR_SUBSCRIBER_COOKIES"
Do the same for AJAX endpoints. Plugins register AJAX actions like wp_ajax_bookingplugin_get_entries. Trigger them with:
curl -s "https://staging.yourdomain.com/wp-admin/admin-ajax.php?action=bookingplugin_get_entries" -H "Cookie: PASTE_YOUR_SUBSCRIBER_COOKIES"
Step 4: Test for IDOR by changing IDs
If an endpoint accepts an ID, increment it. Say your subscriber submitted entry 101. Try ?entry_id=100, 99, 1. If you can read entries you never created, the plugin has an insecure direct object reference. This single test catches a huge share of real-world leaks.
A worked example: finding a real leak in 15 minutes
Let me make this concrete. Say you're auditing a fictional but very realistic plugin called QuickForms, which stores form submissions and displays them in the admin. Here's exactly what a 15-minute audit looked like on a staging site.
- Minute 0–2. I hit
/wp-json/and found the namespacequickforms/v1with two routes:/submissionsand/submissions/(?P<id>\d+). - Minute 3–5. Logged out entirely, I requested
/wp-json/quickforms/v1/submissions. It returned401. Good sign so far. - Minute 6–9. I logged in as
tester@example.com(a subscriber), copied the cookies, and re-ran the request. This time it returned a JSON array of 238 submissions, complete with names, emails, and phone numbers from every form on the site. - Minute 10–12. I checked the source. The route's
permission_callbackwasfunction() { return is_user_logged_in(); }. That's the bug: it checked whether you were logged in, not whether you had permission. - Minute 13–15. I confirmed IDOR too.
/submissions/12returned a submission I never made.
Before: a plugin the site owner assumed was safe. After: a confirmed leak exposing 238 people's personal data to anyone who registered a free account, which under GDPR could mean mandatory breach notification and fines. The fix on the plugin's side is one line: replace is_user_logged_in() with current_user_can('manage_options'). But you can only get that fixed if you find it first.
Reading the source code for the tell-tale signs
Endpoint probing tells you that a leak exists. The source tells you why, and helps you catch leaks your black-box testing missed. You don't need to be a developer to spot the red flags.
What to grep for
Unzip the plugin and search the codebase for these strings:
permission_callback— then check what follows.__return_trueoris_user_logged_inon a data-returning route is a warning.current_user_can— count how often it appears. A plugin that returns sensitive data but rarely calls this is suspect.wp_ajax_— every AJAX handler should verify capability and a nonce.$wpdb->get_results— look for queries that pull records without a user-ownership condition.check_ajax_refererandwp_verify_nonce— their absence near data handlers is a red flag.
A safe pattern vs a leaky pattern
Compare these two permission_callback examples. The first leaks, the second doesn't:
- Leaky:
'permission_callback' => '__return_true' - Leaky:
'permission_callback' => 'is_user_logged_in' - Safe:
'permission_callback' => function() { return current_user_can('edit_others_posts'); }Cover image: Software value feedback loop by jakuza, licensed under BY-SA 2.0 via Openverse.








