wpultimatesecurity/wordpress-security-skills

capability-permission-checks

Use when adding admin pages, menu items, AJAX/REST handlers, action links, or any code that performs a privileged operation in WordPress.

查看源码

使用方法

  1. 复制安装命令,在自己的智能体环境安装前先检查源码包。
  2. 从原始文档核对所需工具、凭据和支持的宿主。
  3. 说明输入材料与期望交付物,并对照来源检查输出。
npx skills add https://github.com/wpultimatesecurity/wordpress-security-skills --skill capability-permission-checks

开始前需了解

  • 安装不包含模型订阅、API 额度或访问文件的权限。
  • 仓库星数和安装量反映活跃情况,不等于独立质量或安全测试。
仓库原始内容

按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。

Capability & permission checks (authorization)

When to use this skill

Use this skill wherever code does something not everyone should be able to do:

  • Admin menu/submenu pages and settings screens.
  • AJAX (wp_ajax_*) and REST permission_callbacks.
  • admin-post.php handlers and action links (delete/approve/activate).
  • Editing options, users, posts, taxonomies, files, or plugin state.
  • Anything reading or writing another user's data.

Authorization answers "is this user allowed?" — distinct from the nonce, which answers "did this request come from us?" (see nonces-csrf-protection). Use both.

Core principles (and why they matter)

  1. Check capabilities, not roles. Roles are bundles of capabilities that admins can

reassign. current_user_can( 'administrator' ) is a bug — test the capability (manage_options), so custom roles and multisite behave correctly.

  1. Use the most specific capability. manage_options for settings, edit_posts to

create posts, edit_post/delete_post (with an ID) for a specific post. Broad caps over-grant.

  1. Authorization and CSRF are independent. A capability check without a nonce is

CSRF-able; a nonce without a capability check lets any logged-in user act. Pair them.

  1. Re-check at every entry point. Hiding a menu item or button is UX, not security.

The handler that does the work must check again — the URL/endpoint is directly reachable.

  1. Per-object checks need the object ID. current_user_can( 'edit_post', $post_id )

respects ownership and post-type rules; current_user_can( 'edit_posts' ) alone does not.

  1. Fail closed. Deny by default; only proceed when the check explicitly passes.

Step-by-step implementation

  1. Identify the operation and the capability it requires (use a meta cap with an ID

for per-object actions).

  1. At the start of the handler, after the nonce check:

if ( ! current_user_can( $cap[, $object_id] ) ) { deny(); }.

  1. deny() = wp_die( ..., 403 ), wp_send_json_error( ..., 403 ), or a REST

WP_Error with rest_forbidden / status 403.

  1. For menus/REST, supply the capability where the API expects it

(add_menu_page capability arg, REST permission_callback).

  1. Never gate solely by hiding UI.

Supporting references

ReferenceLoad when
Capability check examplesImplementing capability-gated admin pages, AJAX handlers, per-object actions, and REST callbacks.
Capability / authorization checklistBefore final verification of the capability / authorization controls.

Common AI mistakes / anti-patterns

Mistake 1 — Checking the role instead of the capability

php
// ❌ Insecure: roles are mutable; this misses custom/multisite setups.
if ( in_array( 'administrator', wp_get_current_user()->roles, true ) ) {
    do_admin_thing();
}
php
// ✅ Secure: test the capability.
if ( current_user_can( 'manage_options' ) ) {
    do_admin_thing();
}

Mistake 2 — Relying on a hidden menu/button as the boundary

php
// ❌ Insecure: handler trusts that only admins saw the link.
add_action( 'admin_post_purge_cache', function () {
    purge_cache(); // directly reachable via admin-post.php by anyone logged in
} );
php
// ✅ Secure: the handler re-checks nonce + capability.
add_action( 'admin_post_purge_cache', function () {
    check_admin_referer( 'purge_cache' );
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( esc_html__( 'Forbidden', 'my-plugin' ), 403 );
    }
    purge_cache();
} );

Mistake 3 — Generic capability where a per-object check is needed

php
// ❌ Insecure: any author can edit ANY post, not just their own.
if ( current_user_can( 'edit_posts' ) ) {
    wp_update_post( array( 'ID' => $post_id, 'post_title' => $title ) );
}
php
// ✅ Secure: meta capability with the object ID enforces ownership.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
    wp_die( esc_html__( 'You cannot edit this post.', 'my-plugin' ), 403 );
}
wp_update_post( array( 'ID' => $post_id, 'post_title' => $title ) );

Mistake 4 — REST route with no real permission callback

php
// ❌ Insecure: open to the world.
register_rest_route( 'my/v1', '/settings', array(
    'methods'             => 'POST',
    'callback'            => 'my_save',
    'permission_callback' => '__return_true',
) );
php
// ✅ Secure: enforce the capability in permission_callback.
register_rest_route( 'my/v1', '/settings', array(
    'methods'             => 'POST',
    'callback'            => 'my_save',
    'permission_callback' => function () {
        return current_user_can( 'manage_options' );
    },
) );

Mistake 5 — Checking capability but never branching

php
// ❌ Insecure: result ignored; action runs regardless.
current_user_can( 'delete_users' );
delete_user_account( $id );
php
// ✅ Secure: branch and fail closed.
if ( ! current_user_can( 'delete_users' ) ) {
    wp_die( esc_html__( 'Forbidden', 'my-plugin' ), 403 );
}
delete_user_account( $id );

Mistake 6 — Assigning roles or capabilities from request data

php
// ❌ Insecure: privilege escalation by posting role=administrator.
$user->set_role( $_POST['role'] );
php
// ✅ Secure: validate the role against a hardcoded allowlist.
$allowed_roles = array( 'subscriber', 'contributor', 'author' );
$role          = isset( $_POST['role'] ) ? sanitize_key( wp_unslash( $_POST['role'] ) ) : '';
if ( ! in_array( $role, $allowed_roles, true ) ) {
    wp_die( esc_html__( 'Invalid role.', 'my-plugin' ), 400 );
}
$user->set_role( $role );

Mistake 7 — Directly adding capabilities via user meta

php
// ❌ Insecure: grants arbitrary capabilities.
$user->add_cap( $_POST['capability'] );
php
// ✅ Secure: never add capabilities from input; use role-based allowlists.
$allowed_caps = array( 'edit_posts', 'publish_posts' );
$cap          = isset( $_POST['capability'] ) ? sanitize_key( wp_unslash( $_POST['capability'] ) ) : '';
if ( ! in_array( $cap, $allowed_caps, true ) ) {
    wp_die( esc_html__( 'Invalid capability.', 'my-plugin' ), 400 );
}
$user->add_cap( $cap );

Related: see the multisite-security skill for network vs. site scope and the nonces-csrf-protection skill for pairing these checks with a nonce.

Correct code examples

A complete example covering an admin page, an AJAX handler, a per-object action, and a REST permission callback is in `references/capability-check-example.php`.

php
// Common capabilities and when to use them.
current_user_can( 'manage_options' );          // site settings / options
current_user_can( 'edit_posts' );              // create/edit posts (general)
current_user_can( 'edit_post', $post_id );     // edit THIS post (ownership-aware)
current_user_can( 'publish_posts' );           // publish
current_user_can( 'upload_files' );            // media uploads
current_user_can( 'edit_users' );              // manage other users
current_user_can( 'install_plugins' );         // install/activate plugins

Checklist

  • [ ] Every privileged handler calls current_user_can() and branches on the result.
  • [ ] Capabilities are tested, never roles.
  • [ ] The most specific capability is used; per-object actions pass the object ID.
  • [ ] The check is paired with a nonce verification on state-changing requests.
  • [ ] Admin pages re-check the capability on render (not just at menu registration).
  • [ ] REST routes define a real permission_callback (never __return_true for writes).
  • [ ] AJAX handlers check capability after check_ajax_referer.
  • [ ] Denied requests fail closed with a 403 / WP_Error.

Official references

按任务相关推荐

来自同一仓库

更多 Skills

全部 Skills
wpultimatesecurity
社区

filesystem-security

Use when reading, writing, including, or deleting files from paths that include user input - include / require, readfile, unlink, filegetcontents, or the WPFilesystem API. Validates paths with validatefile, normalizes with wpnormalizepath, confines operations to an allowed base directory, and uses wpdeletefile / WPFilesystem. Prevents path traversal, local file inclusion, and arbitrary file deletion.

安装量
1
GitHub Stars
50
最近更新
9月29日
wpultimatesecurity
社区

multisite-security

Use when writing code that runs on a WordPress multisite network - switchtoblog, network admin pages, getsites, or capabilities that differ between site and network scope. Uses managenetwork / managenetworkoptions and issuperadmin correctly, restores context with restorecurrentblog, isolates per-site data, and never trusts a blog id from input. Prevents cross-site data leakage and network privilege escalation.

安装量
1
GitHub Stars
50
最近更新
9月29日
wpultimatesecurity
社区

nonces-csrf-protection

Use when handling any form submission, AJAX request, admin-post action, settings page, link that triggers an action, or any other user-initiated request in a WordPress plugin or theme. Generates nonces with wpnoncefield / wpcreatenonce and verifies them with checkadminreferer, checkajaxreferer, or wpverifynonce, always paired with a capability check, to prevent CSRF. Apply proactively whenever code accepts or acts on a request.

安装量
1
GitHub Stars
50
最近更新
9月29日
wpultimatesecurity
社区

secrets-credentials-management

Use when handling passwords, API keys, tokens, or third-party credentials in a WordPress plugin or theme. Hashes passwords with wphashpassword / wpcheckpassword, generates tokens with wpgeneratepassword, keeps secrets out of code and the database in plaintext, and uses Application Passwords for API auth. Prevents credential leakage and insecure storage.

安装量
1
GitHub Stars
50
最近更新
9月29日