Security Overview

Security considerations and best practices for WPZylos plugins.

Security Principles

  1. Never trust user input — Validate and sanitize all input
  2. Always escape output — Escape all data before display
  3. Use prepared statements — Prevent SQL injection
  4. Verify capabilities — Check user permissions
  5. Verify intent — Use nonces for all state-changing actions

Input Security

Sanitization

Clean input before use:

use WPZylos\Framework\Security\Sanitizer;

$sanitizer = new Sanitizer();

$title = $sanitizer->text($_POST['title']);       // Plain text
$content = $sanitizer->html($_POST['content']);   // Safe HTML
$email = $sanitizer->email($_POST['email']);      // Email
$url = $sanitizer->url($_POST['url']);            // URL
$number = $sanitizer->int($_POST['count']);       // Integer

Validation

Verify input meets requirements:

use WPZylos\Framework\Validation\Validator;

$validator = new Validator();

$result = $validator->validate($_POST, [
    'email' => 'required|email',
    'age' => 'required|numeric|min:18',
    'role' => 'required|in:admin,editor,user',
]);

if ($result->fails()) {
    // Handle validation errors
}

Output Security

Always escape output. Use the appropriate function:

FunctionUse For
esc_html()Text content
esc_attr()HTML attributes
esc_url()URLs
esc_js()JavaScript strings
esc_textarea()Textarea content
wp_kses_post()Post content (safe HTML)
wp_kses()Custom allowed HTML
<h1><?php echo esc_html($title); ?></h1>
<input type="text" value="<?php echo esc_attr($value); ?>">
<a href="<?php echo esc_url($link); ?>">Link</a>

Authentication & Authorization

Capability Checks

use WPZylos\Framework\Security\Gate;

$gate = new Gate();

// Check capability
if (!$gate->can('manage_options')) {
    wp_die('Unauthorized');
}

// Check with object
if (!$gate->can('edit_post', $postId)) {
    wp_die('Cannot edit this post');
}

Nonce Verification

use WPZylos\Framework\Security\Nonce;

$nonce = new Nonce($context);

// Create nonce (in form)
$token = $nonce->create('save_settings');

// Verify nonce (on submit)
if (!$nonce->verify($_POST['_nonce'], 'save_settings')) {
    wp_die('Security check failed');
}

Database Security

Prepared Statements

Always use prepared statements:

// Bad: Vulnerable to SQL injection
$wpdb->query("SELECT * FROM users WHERE id = " . $_GET['id']);

// Good: Safe with preparation
$wpdb->get_row($wpdb->prepare(
    "SELECT * FROM users WHERE id = %d",
    (int) $_GET['id']
));

// Good: Safe with QueryBuilder
$builder->where('id', $_GET['id'])->first();

Safe Identifiers

Table and column names cannot be prepared. Whitelist them:

$allowed = ['name', 'email', 'created_at'];
$column = in_array($_GET['sort'], $allowed) ? $_GET['sort'] : 'created_at';

$builder->orderBy($column);

File Upload Security

use WPZylos\Framework\Security\UploadSecurity;

$uploader = new UploadSecurity($context, $nonce, $gate);

// Restrict file types
$uploader->allowMimes([
    'jpg|jpeg' => 'image/jpeg',
    'png' => 'image/png',
    'pdf' => 'application/pdf',
]);

// Restrict file size
$uploader->maxSize(5 * 1024 * 1024); // 5MB

$result = $uploader->handle($_FILES['upload'], 'upload_action');

if (is_wp_error($result)) {
    wp_die($result->get_error_message());
}

AJAX Security

Always verify nonces and capabilities in AJAX handlers:

add_action('wp_ajax_mp_save', function () use ($nonce, $gate, $sanitizer) {
    // 1. Verify nonce
    if (!$nonce->verify($_POST['_nonce'] ?? '', 'save')) {
        wp_send_json_error('Invalid nonce', 403);
    }

    // 2. Check capability
    if (!$gate->can('manage_options')) {
        wp_send_json_error('Unauthorized', 403);
    }

    // 3. Sanitize input
    $data = $sanitizer->text($_POST['data'] ?? '');

    // 4. Process request...

    wp_send_json_success();
});

Rate Limiting

Prevent brute force and abuse:

use WPZylos\Framework\Security\RateLimiter;

$limiter = new RateLimiter($context, 5, 60); // 5 attempts per 60 seconds

$key = 'login_' . $_SERVER['REMOTE_ADDR'];

if ($limiter->tooManyAttempts($key)) {
    $wait = $limiter->availableIn($key);
    wp_die("Too many attempts. Try again in {$wait} seconds.");
}

$limiter->hit($key);

Security Checklist

  • All user input is sanitized
  • All output is escaped
  • All database queries use prepared statements
  • Nonces protect all state-changing actions
  • Capability checks protect sensitive operations
  • File uploads restrict allowed types and sizes
  • AJAX handlers verify nonces and capabilities
  • Rate limiting protects sensitive endpoints
  • No sensitive data in error messages
  • Direct file access is prevented