Claude
Skills
Sign in
Back

wordpress-security-validation

Included with Lifetime
$97 forever

Security-first WordPress development with nonces, sanitization, validation, and escaping to prevent XSS, CSRF, and SQL injection vulnerabilities.

Web Dev

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" opera

Related in Web Dev