☰
WEB SECURITY / PRACTICE

How to Protect the WordPress REST API from Data Leaks

How to Protect the WordPress REST API from Data Leaks

The WordPress REST API is used for more than just third-party integrations. Gutenberg, Elementor, mobile applications, external services, and some plugins rely on it. As a result, completely disabling the API often creates more problems than it solves.

The risk arises when publicly accessible endpoints return more data than the site actually needs. For example, the /wp-json/wp/v2/users route can expose author IDs, names, and slugs. This is not an immediate compromise, but it can make it easier to prepare attacks against user accounts. Protection should focus not on mechanically blocking the entire API, but on controlling specific routes, roles, and response fields.

What Is the REST API and Where Does the Risk Arise?

The REST API is a set of HTTP routes through which WordPress receives or returns data in JSON format. Its base address usually looks like /wp-json/, while standard routes for posts and users are located in the /wp/v2/ namespace.

Some endpoints should genuinely be publicly accessible. For example, the frontend may need to retrieve published posts or pages. However, a public route should not return drafts, private posts, internal metadata fields, email addresses, tokens, or data that visitors do not need.

The most common problems are associated with three scenarios: a standard route exposes unnecessary information, a custom route is registered without a permission_callback, or an endpoint returns private data because of an error in capability checks.

  • a public list of users or authors without a functional reason;
  • access to private post types, drafts, and internal taxonomies;
  • exposure of email addresses, phone numbers, internal IDs, or confidential metadata fields;
  • custom routes that check only whether a user is authenticated, but not the user's role.

Start by Auditing Publicly Accessible Endpoints

The audit should be performed in two modes: as a guest and as a regular authenticated user without administrative privileges. An administrator often sees more because of WordPress access rules, so an administrator session does not show the actual access level available to other users.

First, open /wp-json/ in a browser or check it using a command-line tool. It returns a list of available namespaces and routes. Then check only the endpoints the site actually uses: posts, pages, media, users, custom post types, and plugin routes.

For automated checks, you can use curl. A request without cookies shows what is available to a regular visitor:

  • check /wp-json/wp/v2/users and route variants containing a specific ID;
  • check /wp-json/wp/v2/posts, pages, and registered custom post types;
  • try the per_page, search, author, include, and _embed parameters;
  • compare the responses for a guest, a regular user, and an administrator;
  • check the HTTP status code, JSON structure, and fields that the frontend does not need.
curl -i 'https://example.com/wp-json/wp/v2/users?per_page=10'

curl -i 'https://example.com/wp-json/wp/v2/posts?per_page=5&_embed'

What to Look for in the Response

The mere presence of an author ID or name does not always indicate a critical data leak: these fields may be needed to display authorship. The context is what matters. If an endpoint allows users to be enumerated, exposes internal fields, or provides access to unpublished content, it should be restricted.

Pay particular attention to responses after installing plugins, creating custom post types, or connecting a CRM or mobile application. Custom routes are where proper access checks are most often missing.

How to Properly Restrict the User List

Not every site needs a public list of users. If authors are not displayed on the frontend, the /wp/v2/users route can be blocked for guests or for all roles that do not need it. Before blocking it, however, test Gutenberg, Elementor, author search, and third-party integrations.

A safer approach is to deny access to users who do not have the required capability instead of disabling the REST API entirely. The example below blocks access to user routes for anyone who does not have the list_users capability. Add this code as part of a small custom plugin or an mu-plugin, not directly to the theme file.

This is an access policy example, not a universal rule. On a site where editors or Elementor retrieve a list of authors, this restriction may change functionality. After implementing it, be sure to test the editor and any required integrations.

  • do not merely hide the endpoint link—the check must be performed on the server;
  • do not rely on the _fields parameter as an authorization mechanism;
  • if the list is required, return only the minimum set of fields, such as an ID and public name;
  • also check access to an individual user through the /wp/v2/users/{id} route.
add_filter('rest_pre_dispatch', function ($result, $server, $request) {
    $route = $request->get_route();

    if (preg_match('#^/wp/v2/users(?:/|$)#', $route)
        && ! current_user_can('list_users')) {
        return new WP_Error(
            'rest_forbidden',
            'Access denied.',
            ['status' => 403]
        );
    }

    return $result;
}, 10, 3);

Protect Custom REST Routes

Every custom route should have a permission_callback. This callback determines whether the current request may access the endpoint. Checking only is_user_logged_in() is not sufficient: any authenticated user may have fewer privileges than an administrator or a manager of a particular process.

For read operations, use a capability that corresponds to the data. For operations that modify or delete information, apply stricter permission checks and additionally validate a nonce or another authentication mechanism, depending on how the integration works.

Public routes should also return only the data that is necessary. Do not pass the entire user or post object if the frontend needs only a few values.

  • for administrative data, use current_user_can('manage_options') or a more precise capability;
  • when accessing a specific post, check not only the role, but also the status, author, and access rules;
  • for custom post types, set show_in_rest to true only when REST access is genuinely required;
  • validate and sanitize request parameters, especially IDs, search strings, and filter values.
register_rest_route('site/v1', '/reports', [
    'methods'  => WP_REST_Server::READABLE,
    'callback' => 'site_get_reports',
    'permission_callback' => function () {
        return current_user_can('manage_options');
    },
]);

Prevent Metadata and Private Content Leaks

The REST API may return more than a post title and text. A response can include author, featured_media, navigation links, embedded objects, and registered metadata fields. If metadata is registered with show_in_rest, it may become available through the API subject to its authorization rules.

Review all metadata fields used by SEO plugins, CRMs, forms, stores, and custom development. Passwords, tokens, keys, internal notes, customer data, and internal flags should not be returned in a REST response, even to an authenticated user, unless there is a specific need.

The _fields parameter can reduce the response size and hide unnecessary fields from a particular client, but it does not replace permission_callback. Users can change request parameters, so access rights must be checked before the response is generated.

  • check whether drafts, scheduled posts, and private posts are accessible;
  • review metadata registration and its auth_callback function;
  • do not return email addresses or other personal data without a clear functional need;
  • restrict _embed if embedded objects expose unnecessary information;
  • make sure API errors do not contain SQL queries, file paths, or internal diagnostics.

Authentication, Nonces, and CORS

WordPress cookie authentication and REST nonces protect authenticated-user actions from forged requests in the browser, but a nonce does not make public data private. If an endpoint is available to guests, its response remains public regardless of whether a nonce is present.

For external integrations, use a separate authentication scheme with minimum privileges. Do not pass administrative cookies or passwords in frontend JavaScript. Application Passwords and other credentials should be stored on the integration server, not in page code.

CORS is not a substitute for authorization. A header that allows requests from another domain only defines browser behavior. If an endpoint lacks proper permission checks, an overly broad CORS policy can make it easier for third-party sites to exploit exposed data.

  • allow CORS only for specific trusted domains if it is genuinely required;
  • do not use a wildcard for private responses that use cookies or authentication;
  • revoke integration keys that are no longer used;
  • separate public routes from routes intended for internal services.

Test the Site After Making Changes Without Breaking the Editor

REST API protection is not complete after adding a single rule. You need to retest the roles and workflows that depend on the API. A guest should receive only public data, a regular user only the resources they are allowed to access, and denied actions should return 401 or 403 rather than a complete JSON response containing data.

Also test Gutenberg, Elementor, forms, search, filters, the media library, author blocks, and connected services. If the editor stops working after access is restricted, do not open the entire API without limitations. Identify the specific route, determine the required capability, and allow only the necessary workflow.

During an audit, it is useful to maintain a list of allowed routes and repeat the checks periodically after updating WordPress, plugins, or custom code.

  • clear the cache after making changes and test the API without old cookies;
  • test requests using different roles, not only an administrator account;
  • check the error log and web server logs for unusual API requests;
  • keep WordPress, plugins, and the theme updated, since access-control issues are often fixed in updates;
  • do not hide an endpoint through robots.txt: this does not restrict technical access.

Frequently asked questions

Do I need to disable the WordPress REST API completely?

Usually not. Completely disabling it can disrupt Gutenberg, Elementor, mobile applications, and integrations. It is better to restrict specific routes, posts, metadata fields, and operations that the site does not need.

Is access to /wp-json/wp/v2/users a critical vulnerability?

This route alone does not indicate a compromise. However, it may expose public identifiers and make attack preparation easier. If the user list is not needed, it should be closed to guests or users without the required capability.

Will a nonce prevent data leaks through the REST API?

No. A nonce helps validate a request in a particular authentication scenario, but it does not replace permission checks or hide data from a public endpoint.

Can I hide unnecessary fields using the _fields parameter?

The _fields parameter is useful for reducing the response, but it is not a security mechanism. Users can change the request, so confidential fields must be protected when the response is generated and through permission checks.

What should I do if Elementor stops working after restricting the API?

Check the editor's requests in the browser developer tools, identify the specific route, and determine the required role. Open only that endpoint with a correct permission_callback instead of opening the entire REST API.

Conclusion

WordPress REST API protection starts with inventorying routes and checking responses for different roles. Close unnecessary access to users, private posts, and metadata; add permission_callback to custom endpoints; and return only the minimum fields required.

Do not disable the REST API indiscriminately. A combination of precise capability checks, secure authentication, restricted CORS, and regular testing can reduce data exposure without harming Gutenberg, Elementor, or essential integrations.

← All web security materials