# Wordpress Security

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-iwritec0de-wp-dev-wordpress-security`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [iwritec0de](https://agentstack.voostack.com/s/iwritec0de)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [iwritec0de](https://github.com/iwritec0de)
- **Source:** https://github.com/iwritec0de/wp-dev/tree/main/skills/wordpress-security

## Install

```sh
agentstack add skill-iwritec0de-wp-dev-wordpress-security
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# WordPress Security Best Practices

This skill covers WordPress security patterns including output escaping, input sanitization, nonce verification, capability checks, and secure database queries.

## Output Escaping

Every piece of dynamic data rendered in HTML must be escaped. Choose the function matching the output context:

| Context | Function | Example |
|---------|----------|---------|
| HTML body | `esc_html()` | `` |
| HTML attribute | `esc_attr()` | `">` |
| URL/href | `esc_url()` | `">` |
| Textarea content | `esc_textarea()` | `` |
| Inline JS value | `esc_js()` | `onclick="alert('')"` |
| Rich HTML (post content) | `wp_kses_post()` | `` |
| Custom allowed HTML | `wp_kses()` | `echo wp_kses( $html, $allowed_tags );` |

**Translation + escaping combos:**
- `esc_html__()` / `esc_html_e()` — translatable escaped strings
- `esc_attr__()` / `esc_attr_e()` — translatable escaped attributes
- `wp_kses_post()` on `__()` output for rich translated content

## Input Sanitization

Sanitize all user input immediately upon receipt, before storage or processing:

| Data Type | Function |
|-----------|----------|
| Plain text | `sanitize_text_field( wp_unslash( $_POST['field'] ) )` |
| Textarea | `sanitize_textarea_field( wp_unslash( $_POST['field'] ) )` |
| Email | `sanitize_email( $_POST['email'] )` |
| Integer | `absint( $_POST['id'] )` or `intval( $_POST['num'] )` |
| Filename | `sanitize_file_name( $_FILES['file']['name'] )` |
| HTML class | `sanitize_html_class( $_POST['class'] )` |
| Key/slug | `sanitize_key( $_POST['key'] )` |
| Title/slug | `sanitize_title( $_POST['title'] )` |
| URL | `esc_url_raw( $_POST['url'] )` (for storage — use `esc_url()` for display) |

**Always `wp_unslash()` superglobals** before sanitizing — WordPress adds slashes to `$_GET`, `$_POST`, `$_REQUEST`.

## Nonce Verification

Every form submission and AJAX request must include a nonce for CSRF protection:

### Forms
```php
// In the form template:
wp_nonce_field( 'myplugin_save_action', 'myplugin_nonce' );

// In the handler:
if ( ! isset( $_POST['myplugin_nonce'] ) ||
     ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['myplugin_nonce'] ) ), 'myplugin_save_action' ) ) {
    wp_die( esc_html__( 'Security check failed.', 'myplugin' ) );
}
```

### AJAX
```php
// Enqueue with nonce:
wp_localize_script( 'myplugin-script', 'myPluginAjax', array(
    'nonce' => wp_create_nonce( 'myplugin_ajax_nonce' ),
    'url'   => admin_url( 'admin-ajax.php' ),
) );

// In the AJAX handler:
check_ajax_referer( 'myplugin_ajax_nonce', 'nonce' );
```

### Admin pages
```php
check_admin_referer( 'myplugin_settings_action', 'myplugin_settings_nonce' );
```

## Capability Checks

Always verify the current user has permission before performing privileged operations:

```php
// Before saving settings:
if ( ! current_user_can( 'manage_options' ) ) {
    wp_die( esc_html__( 'Unauthorized.', 'myplugin' ) );
}

// Before editing posts:
if ( ! current_user_can( 'edit_post', $post_id ) ) {
    wp_die( esc_html__( 'Unauthorized.', 'myplugin' ) );
}
```

Common capabilities: `manage_options`, `edit_posts`, `publish_posts`, `edit_others_posts`, `delete_posts`, `upload_files`, `manage_categories`, `edit_users`.

## Database Security

Never pass unsanitized data into SQL. Always use `$wpdb->prepare()`:

```php
global $wpdb;

// Parameterized query:
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}custom_table WHERE user_id = %d AND status = %s",
        $user_id,
        $status
    )
);

// Prefer CRUD methods for single-row operations:
$wpdb->insert( $wpdb->prefix . 'custom_table', array(
    'user_id' => $user_id,
    'status'  => $status,
), array( '%d', '%s' ) );

$wpdb->update( $wpdb->prefix . 'custom_table',
    array( 'status' => 'active' ),
    array( 'id' => $row_id ),
    array( '%s' ),
    array( '%d' )
);

$wpdb->delete( $wpdb->prefix . 'custom_table',
    array( 'id' => $row_id ),
    array( '%d' )
);
```

## File Operations

Use the WP_Filesystem API instead of direct PHP file functions:

```php
global $wp_filesystem;
WP_Filesystem();

$wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE );
$content = $wp_filesystem->get_contents( $file_path );
```

For uploads, use `wp_handle_upload()`:

```php
$uploaded = wp_handle_upload( $_FILES['myfile'], array( 'test_form' => false ) );
if ( isset( $uploaded['error'] ) ) {
    wp_die( esc_html( $uploaded['error'] ) );
}
```

## AJAX & REST Security

### AJAX handlers
```php
add_action( 'wp_ajax_myplugin_action', 'myplugin_ajax_handler' );

function myplugin_ajax_handler(): void {
    check_ajax_referer( 'myplugin_nonce', 'nonce' );

    if ( ! current_user_can( 'edit_posts' ) ) {
        wp_send_json_error( 'Unauthorized', 403 );
    }

    $data = sanitize_text_field( wp_unslash( $_POST['data'] ) );
    // ... process ...

    wp_send_json_success( array( 'result' => $data ) );
}
```

### REST endpoints
```php
register_rest_route( 'myplugin/v1', '/items', array(
    'methods'             => 'GET',
    'callback'            => 'myplugin_get_items',
    'permission_callback' => function (): bool {
        return current_user_can( 'read' );
    },
) );
```

**Never set `permission_callback` to `__return_true`** unless the endpoint is intentionally public.

## Redirects & JSON Responses

```php
// Safe redirect (restricts to allowed hosts):
wp_safe_redirect( admin_url( 'admin.php?page=myplugin' ) );
exit;

// JSON responses:
wp_send_json_success( $data );
wp_send_json_error( $message, $status_code );

// Terminate execution properly:
wp_die( $message, $title, array( 'response' => 403 ) );
```

For the full function reference with signatures and examples, see `references/security-functions.md`.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [iwritec0de](https://github.com/iwritec0de)
- **Source:** [iwritec0de/wp-dev](https://github.com/iwritec0de/wp-dev)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-iwritec0de-wp-dev-wordpress-security
- Seller: https://agentstack.voostack.com/s/iwritec0de
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
