wordpress-security-validation
Security-first WordPress development with nonces, sanitization, validation, and escaping to prevent XSS, CSRF, and SQL injection vulnerabilities.
What this skill does
# WordPress Security & Data Validation
**Version:** 1.0.0
**Target:** WordPress 6.7+ | PHP 8.3+
**Skill Level:** Intermediate to Advanced
## Overview
Security is not optional in WordPress development—it's fundamental. This skill teaches the **three-layer security model** that prevents XSS, CSRF, SQL injection, and other common web vulnerabilities through proper input sanitization, business logic validation, and output escaping.
**The Golden Rule:** "Sanitize on input, validate for logic, escape on output."
### Why This Matters
Every year, thousands of WordPress sites are compromised due to security vulnerabilities in plugins and themes. Most of these attacks exploit one of three weaknesses:
1. **XSS (Cross-Site Scripting):** Malicious JavaScript injected through unsanitized output
2. **CSRF (Cross-Site Request Forgery):** Unauthorized actions performed on behalf of authenticated users
3. **SQL Injection:** Database manipulation through unsanitized database queries
This skill provides **complete, production-ready patterns** for preventing all three attack vectors.
---
## The Three-Layer Security Model
WordPress security follows a defense-in-depth strategy with three distinct layers:
```
User Input → [1. SANITIZE] → [2. VALIDATE] → Process → [3. ESCAPE] → Output
```
### Layer 1: Sanitization (Input Cleaning)
**Purpose:** Remove dangerous characters and normalize data format
**When:** Immediately upon receiving user input
**Example:** `sanitize_text_field($_POST['username'])`
### Layer 2: Validation (Logic Checks)
**Purpose:** Ensure data meets business requirements
**When:** After sanitization, before processing
**Example:** `if (!is_email($email)) { /* error */ }`
### Layer 3: Escaping (Output Protection)
**Purpose:** Prevent XSS by encoding special characters
**When:** Every time you output data to browser
**Example:** `echo esc_html($user_input);`
**Critical Distinction:**
- **Sanitization** removes/transforms invalid data (changes the value)
- **Validation** checks if data is acceptable (returns true/false)
- **Escaping** makes data safe for display (context-specific encoding)
---
## 1. Nonces: CSRF Protection
### What Are Nonces?
Nonces (Numbers Used Once) are cryptographic tokens that verify a request originated from your site, not a malicious external source. They prevent **Cross-Site Request Forgery (CSRF)** attacks.
**How CSRF Attacks Work:**
```html
<!-- Attacker's malicious site: evil.com -->
<img src="https://yoursite.com/wp-admin/admin.php?action=delete_user&id=1">
<!-- If user is logged into yoursite.com, this executes! -->
```
**How Nonces Prevent CSRF:**
```html
<!-- Legitimate request with nonce -->
<form action="admin.php?action=delete_user&id=1" method="POST">
<?php wp_nonce_field('delete_user_1', 'delete_nonce'); ?>
<button>Delete User</button>
</form>
<!-- Attacker cannot generate valid nonce (tied to user session) -->
```
### Nonce Implementation Patterns
#### Pattern 1: Form Nonces (Most Common)
**BEFORE (Vulnerable):**
```php
// Vulnerable form processing
if (isset($_POST['submit'])) {
$user_id = absint($_POST['user_id']);
delete_user($user_id); // ⚠️ CSRF vulnerable!
}
```
**AFTER (Secure):**
```php
// Generate nonce in form
<form method="post" action="">
<?php wp_nonce_field('delete_user_action', 'delete_user_nonce'); ?>
<input type="hidden" name="user_id" value="42">
<button type="submit" name="submit">Delete User</button>
</form>
// Verify nonce on submission
if (isset($_POST['submit'])) {
// Security check #1: Verify nonce
if (!isset($_POST['delete_user_nonce']) ||
!wp_verify_nonce($_POST['delete_user_nonce'], 'delete_user_action')) {
wp_die('Security check failed: Invalid nonce');
}
// Security check #2: Capability check
if (!current_user_can('delete_users')) {
wp_die('You do not have permission to delete users');
}
// Now safe to process
$user_id = absint($_POST['user_id']);
wp_delete_user($user_id);
}
```
**Key Functions:**
- `wp_nonce_field($action, $name)` - Generates hidden nonce field
- `wp_verify_nonce($nonce, $action)` - Verifies nonce validity
#### Pattern 2: URL Nonces
**Use Case:** Delete/trash links, admin actions
```php
// Generate nonce URL
$delete_url = wp_nonce_url(
admin_url('admin.php?action=delete_post&post_id=123'),
'delete_post_123', // Action (must be unique)
'delete_nonce' // Query parameter name
);
echo '<a href="' . esc_url($delete_url) . '">Delete Post</a>';
// Verify nonce in handler
add_action('admin_action_delete_post', 'handle_delete_post');
function handle_delete_post() {
// Verify nonce from URL
if (!isset($_GET['delete_nonce']) ||
!wp_verify_nonce($_GET['delete_nonce'], 'delete_post_123')) {
wp_die('Invalid security token');
}
// Verify capability
$post_id = absint($_GET['post_id']);
if (!current_user_can('delete_post', $post_id)) {
wp_die('You cannot delete this post');
}
// Delete post
wp_delete_post($post_id, true); // true = force delete
// Redirect with success message
wp_redirect(add_query_arg('message', 'deleted', wp_get_referer()));
exit;
}
```
#### Pattern 3: AJAX Nonces
**Use Case:** Frontend AJAX requests
**BEFORE (Vulnerable):**
```javascript
// ⚠️ Vulnerable AJAX request
jQuery.post(ajaxurl, {
action: 'update_user_meta',
user_id: 42,
meta_key: 'favorite_color',
meta_value: 'blue'
}, function(response) {
console.log(response);
});
```
**AFTER (Secure):**
**PHP (Enqueue script with nonce):**
```php
add_action('wp_enqueue_scripts', 'enqueue_ajax_script');
function enqueue_ajax_script() {
wp_enqueue_script('my-ajax-script',
plugin_dir_url(__FILE__) . 'js/ajax.js',
['jquery'],
'1.0.0',
true
);
// Pass nonce and AJAX URL to JavaScript
wp_localize_script('my-ajax-script', 'myAjax', [
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('my_ajax_nonce'), // Generate nonce
]);
}
// AJAX handler with nonce verification
add_action('wp_ajax_update_user_meta', 'handle_ajax_update');
function handle_ajax_update() {
// Verify nonce
check_ajax_referer('my_ajax_nonce', 'nonce');
// Verify capability
if (!current_user_can('edit_users')) {
wp_send_json_error(['message' => 'Permission denied']);
}
// Sanitize input
$user_id = absint($_POST['user_id']);
$meta_key = sanitize_key($_POST['meta_key']);
$meta_value = sanitize_text_field($_POST['meta_value']);
// Update meta
update_user_meta($user_id, $meta_key, $meta_value);
wp_send_json_success(['message' => 'Updated successfully']);
}
```
**JavaScript (Use nonce in AJAX):**
```javascript
jQuery(document).ready(function($) {
$('#update-button').on('click', function() {
$.post(myAjax.ajaxurl, {
action: 'update_user_meta',
nonce: myAjax.nonce, // Include nonce
user_id: 42,
meta_key: 'favorite_color',
meta_value: 'blue'
}, function(response) {
if (response.success) {
console.log(response.data.message);
} else {
console.error(response.data.message);
}
});
});
});
```
**Key Functions:**
- `wp_create_nonce($action)` - Generate nonce token
- `check_ajax_referer($action, $query_arg)` - Verify AJAX nonce (dies on failure)
- `wp_send_json_success($data)` - Send JSON success response
- `wp_send_json_error($data)` - Send JSON error response
### Nonce Best Practices
✅ **DO:**
- Use unique action names (e.g., `delete_post_$post_id`, not just `delete`)
- Always verify nonces BEFORE processing any data
- Combine nonce checks with capability checks
- Use specific nonce functions (`check_ajax_referer` for AJAX)
❌ **DON'T:**
- Reuse the same nonce action for multiple operations
- Skip nonce verification for "read-only" operaRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.