wordpress-security
Skill0 starsChanged 41 days agoApache-2.0View on GitHub
Security review and secure-coding guidance for WordPress plugins, themes, sites and WooCommerce stores (WordPress 6.x–7.x, WooCommerce 8.x–11.x). Use when auditing, reviewing or hardening WordPress PHP code, or when writing or changing plugin/theme code that handles requests, such as admin-ajax actions, admin-post handlers, REST routes, shortcodes, blocks, settings pages, the Abilities API, $wpdb queries, file uploads, user/role management, or WooCommerce orders, carts, checkout and payment gateways. Triggers on wp-config.php, wp-content/, plugin or theme file headers, or WooCommerce code. Covers capability checks and broken access control, nonces/CSRF, SQL injection ($wpdb->prepare), XSS and escaping, sanitization, PHP object injection, file inclusion and uploads, SSRF and redirects, REST/AJAX exposure, wp-config and secrets, WooCommerce order and price integrity, vulnerable plugins, and fix verification.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- iwritec0de/wp-devtheir own version, first seen Apr 19, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- iwritec0de/wp-dev★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository11 other files it gives its agents.
- appsec-reviewSkill
- aspnet-core-securitySkill
- django-securitySkill
- flutter-securitySkill
- laravel-securitySkill
- nestjs-securitySkill
- nextjs-securitySkill
- nodejs-securitySkill
- react-securitySkill
- spring-boot-securitySkill
- supabase-securitySkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 1 is distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 18
All paths in this skill (references/...) are relative to the directory containing this SKILL.md.
- Very specific: line 25
If the appsec-review skill is installed, it owns the overall methodology and report format.
- Very specific: line 39
Check composer.json / composer.lock if present (Bedrock, modern plugins).
- Distinctive: line 43
Search the code for every way a request reaches plugin code.
- Very specific: line 153
Nonce: create with wp_nonce_field( 'myplugin_save_' .
- Very specific: line 155
SQL: always $wpdb->prepare() with placeholders (%d, %f, %s, %i for identifiers on 6.2+).
- Very specific: line 157
REST: always provide a real permission_callback.
- Very specific: line 159
Files: use wp_handle_upload() with type checking, never trust client names or types, and never include paths built from input.
- Very specific: line 167
Use these when appsec-review is not installed.
- Very specific: line 172
Rules: never invent hooks, files or plugin behavior.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
WordPress Security
Find, explain, fix and verify security issues in WordPress plugins, themes, sites and WooCommerce stores, and write WordPress code that doesn't introduce them.
All paths in this skill (references/...) are relative to the directory containing this SKILL.md.
Modes
- Review mode: audit, security review, plugin vetting, "is this secure?". Follow the workflow below.
- Build mode: writing or modifying plugin/theme code. Apply the build-mode guardrails to every handler you write.
If the appsec-review skill is installed, it owns the overall methodology and report format. This skill supplies the WordPress-specific knowledge.
The WordPress security model in one paragraph
Almost every WordPress vulnerability is one of four mistakes in a request handler: a missing capability check (current_user_can), a missing nonce check (CSRF), unescaped output (XSS), or unprepared SQL. Handlers are reachable in more ways than developers expect. wp_ajax_nopriv_*, admin_post_nopriv_*, admin_init, init, REST routes, shortcodes and blocks all run for unauthenticated or low-privilege users unless the code checks. A nonce proves intent, not permission. is_admin() checks the screen, not the user.
Review workflow
1. Scope and versions
- What is being reviewed: a single plugin/theme (most common), a full site (
wp-config.php,wp-content/), or a WooCommerce extension. - WordPress version:
wp-includes/version.php($wp_version), or the plugin'sRequires at least:header for plugin-only reviews. Only the latest major (7.1 as of 2026-10) is actively supported. Older branches back to 4.7 get security backports on a best-effort basis. - Plugin/theme version: main file header (
Version:),readme.txt(Stable tag:). - WooCommerce:
woocommerce/woocommerce.phpheader, orWC_VERSION. Note HPOS (custom order tables) usage. - Check
composer.json/composer.lockif present (Bedrock, modern plugins).
2. Map entry points
Search the code for every way a request reaches plugin code. This is the most important step.
add_action( 'wp_ajax_…' → logged-in users of ANY role (including subscribers)
add_action( 'wp_ajax_nopriv_…' → unauthenticated visitors
add_action( 'admin_post_…' / 'admin_post_nopriv_…'
add_action( 'admin_init' → also runs for unauthenticated admin-ajax.php / admin-post.php requests
add_action( 'init' / 'wp_loaded' / 'template_redirect' / 'parse_request' reading $_GET/$_POST/$_REQUEST
register_rest_route( → check permission_callback
add_shortcode( / register_block_type( render_callback → run with content authors' input (contributor+)
wp_register_ability( → Abilities API (6.9+), exposed to AI agents via MCP
add_menu_page( / add_submenu_page( → capability argument + the handler that saves settings
register_setting( / options.php → sanitize_callback
add_filter( 'xmlrpc_methods' → custom XML-RPC methods
Direct file access: PHP files without `defined( 'ABSPATH' ) || exit;`
Woo: woocommerce_api_* (wc-api callbacks), Store API extensions, checkout/cart hooksFor each, record: who can reach it, what input it reads, what it changes or returns.
3. Review each area
| Area | Reference | Start by looking for |
| Authorization & capabilities | references/authorization.md | Handlers without current_user_can, is_admin() used as auth, __return_true permission callbacks, update_option/update_user_meta from input |
| Nonces & CSRF | references/nonces-csrf.md | State changes without check_admin_referer/check_ajax_referer/wp_verify_nonce, ignored return values |
| SQL injection | references/sql-injection.md | $wpdb->query/get_results/get_var/get_row/get_col with interpolated variables, esc_sql misuse, ORDER BY from input |
| XSS & escaping | references/xss-escaping.md | echo $… without esc_*, _e()/__() with HTML, shortcode attributes, wp_kses configs, admin notices |
| Input handling & object injection | references/input-validation.md | $_GET/$_POST/$_REQUEST/$_COOKIE/$_SERVER without wp_unslash + sanitize/validate, unserialize/maybe_unserialize on input, extract(), LFI |
| File uploads & file operations | references/file-uploads.md | move_uploaded_file, wp_handle_upload with test_type => false, upload_mimes additions, unzip_file, paths from input |
| REST, AJAX & API exposure | references/rest-ajax-api.md | REST routes, show_in_rest meta/CPTs, Abilities API, XML-RPC, user enumeration, application passwords |
| SSRF & redirects | references/ssrf-redirects.md | wp_remote_get($input), file_get_contents($url), wp_redirect($input) |
| Authentication & accounts | references/authentication.md | Custom login/registration/reset, wp_set_auth_cookie, default_role, password storage, brute force |
| Configuration & secrets | references/secrets-config.md | wp-config.php, debug settings, debug.log, salts, API keys in code/options, file editing, uploads PHP execution |
| WooCommerce | references/woocommerce.md | Order IDOR, price/coupon tampering, payment callbacks, Store API extensions, PII exports |
| Dependencies | references/dependencies.md | Core/plugin/theme versions, known advisories, abandoned or nulled plugins |
| Verification | references/verification.md | WP-CLI, PHPCS security sniffs, Plugin Check, tests, safe requests |
4. Classify, report, fix, verify
Trace every finding from entry point → missing/broken check → sensitive operation, with file:line. Classify using the rules below. When fixing, use WordPress APIs (capabilities, nonces, $wpdb->prepare, esc_*, sanitize_*) and add a test or reproducible request per references/verification.md.
High-signal patterns
Investigation signals, not findings. Trace each one.
# Access control
wp_ajax_nopriv_ admin_post_nopriv_ add_action( 'admin_init' is_admin() && is_user_logged_in() (as the only check)
'permission_callback' => '__return_true' register_rest_route( (no permission_callback)
update_option( $_POST / $request update_user_meta( … $_POST wp_update_user( $_POST wp_insert_user( set_role( add_cap(
wp_set_auth_cookie( wp_set_current_user( get_user_by( 'email', $_…
# CSRF
wp_verify_nonce( without if/! check_ajax_referer( …, false ) nonce created but never verified handlers on GET that change state
# SQL
$wpdb->query( "…$ $wpdb->get_results( "…{$ ->prepare( "…$var…" esc_sql( (unquoted) ORDER BY $ LIMIT $ IN ( $
like_escape( $wpdb->prepare( $sql ) (no args)
# XSS
echo $_ echo $atts[ print $ _e( / __( with variables printf( without esc_ esc_attr( inside href wp_kses( $x, 'post' ) on untrusted html_entity_decode(
add_shortcode( render_callback admin_notices
# Injection / RCE
unserialize( maybe_unserialize( $_ extract( include( $_ require( … $ call_user_func( $_ do_action( $_ apply_filters( $_
eval( create_function( assert( shell_exec( exec( system( passthru( proc_open( popen( preg_replace( …/e
# Files
move_uploaded_file( 'test_form' => false 'test_type' => false upload_mimes unfiltered_upload unzip_file( file_put_contents(
copy( $_ unlink( $_ readfile( $_ file_get_contents( $_ fopen( $_ wp_delete_file( $_
# SSRF / redirect
wp_remote_get( $ wp_remote_post( $ download_url( $ file_get_contents( 'http wp_redirect( $_ header( 'Location: ' . $
# Config
define( 'WP_DEBUG', true ) WP_DEBUG_DISPLAY WP_DEBUG_LOG DISALLOW_FILE_EDIT put your unique phrase here ALLOW_UNFILTERED_UPLOADSCommon false positives
wp_ajax_*withoutcurrent_user_canwhen the action only reads or writes the current user's own data (scoped byget_current_user_id()). That's still worth a nonce check, but it isn't privilege escalation.$wpdb->query()with only constants,$wpdb->prefix/table properties, or values passed throughabsint()/(int)before interpolation.$wpdb->prepare()with%s/%d/%iplaceholders and arguments: safe for those values.%i(6.2+) safely quotes identifiers.- Output passed through
esc_html,esc_attr,esc_url,wp_kses_post,wp_ksesat the point of echo. Also core template tags that escape internally (the_title()runs filters;the_content()is designed to output HTML). Don't flag core-filtered post content as XSS: authors withunfiltered_html(admins/editors on single site) are allowed to post HTML. - Missing nonce on read-only
GEThandlers with no side effects. is_admin()used to decide where to load code, not whether a user may act.maybe_unserialize()on data read from the database that only admins can write. A finding only if lower-privilege or external input reaches it.- REST routes with
__return_truethat are intentionally public and read-only (public post data, health checks). - Placeholder salts (
put your unique phrase here) inwp-config.php: WordPress ignores them and uses random salts stored in the database. Hardening, not Critical. Real, publicly known salt values are the critical case. - User enumeration (
/wp-json/wp/v2/users,?author=1) is Informational/Hardening by WordPress's own security policy, not a vulnerability. - Admins doing admin things (installing plugins, editing code via the file editor, adding unfiltered HTML). WordPress treats administrators as fully trusted on single-site installs. Report only when a lower role or an unauthenticated user can reach the capability.
Severity calibration
| Who can trigger it | Typical severity for impactful issues |
Unauthenticated (nopriv, public REST, front-end init) | Critical/High |
Subscriber / customer (anyone who can register; check users_can_register) | High |
| Contributor / author | Medium (High for stored XSS that executes for admins) |
| Editor / shop manager | Low/Medium |
| Administrator (single site) | Usually not a vulnerability |
Privilege escalation to administrator, arbitrary option update, arbitrary file upload/read/delete, SQL injection and PHP object injection reachable by unauthenticated users or subscribers are Critical.
Build-mode guardrails
For every request handler you write:
- Capability:
if ( ! current_user_can( 'manage_options' ) ) { wp_die( … , 403 ); }(or the narrowest fitting capability). Use meta capabilities for objects:current_user_can( 'edit_post', $post_id ). - Nonce: create with
wp_nonce_field( 'myplugin_save_' . $id )/wp_create_nonce(), verify withcheck_admin_referer()/check_ajax_referer()(both die on failure by default). REST routes rely on cookie auth plus theX-WP-Nonce(wp_rest) nonce automatically. Don't disable that. - Input:
wp_unslash()then sanitize by type:absint,sanitize_text_field,sanitize_email,sanitize_key,sanitize_file_name,esc_url_raw. Validate against allow-lists for enums. - SQL: always
$wpdb->prepare()with placeholders (%d,%f,%s,%ifor identifiers on 6.2+). Use$wpdb->esc_like()for LIKE. PreferWP_Query,get_posts,$wpdb->insert/update/deletewith format arrays. - Output: escape late, at the point of output, for the context:
esc_html,esc_attr,esc_url,esc_textarea,wp_kses_post/wp_kses,wp_json_encodefor JS. Useesc_html__()/esc_html_e()for translated strings. - REST: always provide a real
permission_callback. Declareargswithtype,sanitize_callbackandvalidate_callback. - AJAX: register
wp_ajax_nopriv_*only for genuinely public actions, and never for state changes without a proof of authorization. - Files: use
wp_handle_upload()with type checking, never trust client names or types, and neverincludepaths built from input. - Never
unserialize()user input (use JSON),extract()request data,eval, or dynamic function names from input. - Remote requests:
wp_safe_remote_get()for user-supplied URLs. Redirects:wp_safe_redirect(). - Secrets: constants in
wp-config.phpor environment variables, not hardcoded or autoloaded options readable via REST. - Direct access guard at the top of every PHP file:
defined( 'ABSPATH' ) || exit;
Evidence and reporting rules
Use these when appsec-review is not installed.
- Confidence: Confirmed (entry point → sink fully traced), Likely (one runtime condition unverified: name it, e.g. "requires
users_can_registerenabled"), Hardening, Informational. - Severity: impact × required role (table above) × exposure.
- Finding format: Title (impact in plain words), severity and confidence, location (file:line, hook/function), entry point and required role, evidence (code), impact, preconditions, fix (WordPress API), verification (request/test), references (CWE, WordPress developer docs).
- Rules: never invent hooks, files or plugin behavior. Redact secrets (
AUTH_KEY→****). Say when runtime verification wasn't performed. Only test sites you are authorized to assess, with non-destructive requests.
References
- WordPress Plugin Handbook, Security: https://developer.wordpress.org/plugins/security/
- Common APIs, Security: https://developer.wordpress.org/apis/security/
- REST API Handbook, Adding endpoints: https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/
- WordPress Coding Standards (PHPCS security sniffs): https://github.com/WordPress/WordPress-Coding-Standards
- WordPress security releases: https://wordpress.org/news/category/security/
- WooCommerce developer docs: https://developer.woocommerce.com/docs/
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

