Security Best Practices

Security guidelines for WooAI Chatbot Pro developers.


1. Authentication & Authorization

Nonce Verification

All REST endpoints verify WordPress nonces:

// Correct: Always verify nonce
public function handle_message( $request ) {
    // Nonce auto-verified by WP REST API when using 'permission_callback'
    $session_id = sanitize_text_field( $request['session_id'] );
    // Process...
}

// Registration with permission callback
register_rest_route( 'woo-ai/v1', '/chat/message', [
    'methods'             => 'POST',
    'callback'            => [ $this, 'handle_message' ],
    'permission_callback' => [ $this, 'check_chat_permission' ],
]);

public function check_chat_permission() {
    return wp_verify_nonce(
        $_SERVER['HTTP_X_WP_NONCE'] ?? '',
        'wp_rest'
    );
}

Capability Checks

// Admin endpoints require manage_woocommerce
public function check_admin_permission() {
    return current_user_can( 'manage_woocommerce' );
}

// License endpoints require manage_options
public function check_license_permission() {
    return current_user_can( 'manage_options' );
}

2. Input Sanitization

Text Input

// Always sanitize user input
$message = sanitize_text_field( $request['message'] );
$session_id = sanitize_key( $request['session_id'] );

// For HTML content (admin only)
$content = wp_kses_post( $request['content'] );

// For arrays
$ids = array_map( 'absint', $request['product_ids'] );

Session ID Validation

private function validate_session_id( $session_id ) {
    // Must be 20-50 chars, alphanumeric with dashes
    if ( ! preg_match( '/^[a-zA-Z0-9-]{20,50}$/', $session_id ) ) {
        return new WP_Error(
            'invalid_session_format',
            'Session ID format invalid',
            [ 'status' => 400 ]
        );
    }
    return true;
}

Message Length Limits

// Prevent DoS via large messages
$max_length = apply_filters( 'wooai_max_message_length', 4000 );
if ( strlen( $message ) > $max_length ) {
    return new WP_Error(
        'message_too_long',
        sprintf( 'Message exceeds %d character limit', $max_length ),
        [ 'status' => 400 ]
    );
}

3. Database Security

Prepared Statements

// CORRECT: Always use prepared statements
global $wpdb;
$result = $wpdb->get_row( $wpdb->prepare(
    "SELECT * FROM {$wpdb->prefix}wooai_conversations WHERE session_id = %s",
    $session_id
));

// WRONG: Never concatenate user input
// $wpdb->query( "SELECT * FROM ... WHERE id = " . $_GET['id'] ); // SQL INJECTION!

Table Prefixes

// Always use $wpdb->prefix
$table = $wpdb->prefix . 'wooai_messages';

// Never hardcode wp_ prefix
// $table = 'wp_wooai_messages'; // WRONG

4. API Key Security

Storage

// API keys stored encrypted in options
$encrypted_key = $this->encrypt_api_key( $api_key );
update_option( 'wooai_openai_key', $encrypted_key );

// Never expose in frontend
add_filter( 'wooai_localize_data', function( $data ) {
    unset( $data['api_key'] ); // Remove sensitive data
    return $data;
});

Logging

// Mask API keys in logs
private function mask_api_key( $key ) {
    if ( strlen( $key ) <= 8 ) return '***';
    return substr( $key, 0, 4 ) . '...' . substr( $key, -4 );
}

// Log example: sk-ab...xy12

5. XSS Prevention

Output Escaping

// Always escape output
echo esc_html( $user_message );
echo esc_attr( $class_name );
echo esc_url( $redirect_url );

// For JavaScript
wp_localize_script( 'wooai-chat', 'wooAiChatbot', [
    'session_id' => esc_js( $session_id ),
    'nonce'      => wp_create_nonce( 'wp_rest' ),
]);

React/Frontend

// React auto-escapes by default
<div>{userMessage}</div> // Safe

// Dangerous - avoid unless sanitized on server
<div dangerouslySetInnerHTML={{ __html: content }} />

// Use DOMPurify for HTML content
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} />

6. Rate Limiting

class RateLimiter {
    public function check( $identifier ) {
        $key = 'wooai_rate_' . md5( $identifier );
        $requests = (int) get_transient( $key );
        $limit = apply_filters( 'wooai_rate_limit', 30 );
        $window = apply_filters( 'wooai_rate_window', 60 );

        if ( $requests >= $limit ) {
            return new WP_Error(
                'rate_limit_exceeded',
                'Too many requests. Please wait.',
                [ 'status' => 429 ]
            );
        }

        set_transient( $key, $requests + 1, $window );
        return true;
    }
}

7. CSRF Protection

Forms

// Admin forms
<form method="post">
    <?php wp_nonce_field( 'wooai_settings', 'wooai_nonce' ); ?>
    <!-- fields -->
</form>

// Verification
if ( ! wp_verify_nonce( $_POST['wooai_nonce'], 'wooai_settings' ) ) {
    wp_die( 'Security check failed' );
}

AJAX

// Include nonce in AJAX requests
jQuery.ajax({
    url: ajaxurl,
    data: {
        action: 'wooai_action',
        nonce: wooAiChatbot.nonce,
        // ...
    }
});

8. File Security

Direct Access Prevention

// At top of every PHP file
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

Upload Validation

// If accepting file uploads (future feature)
$allowed_types = [ 'image/jpeg', 'image/png', 'application/pdf' ];
$finfo = finfo_open( FILEINFO_MIME_TYPE );
$mime = finfo_file( $finfo, $_FILES['file']['tmp_name'] );

if ( ! in_array( $mime, $allowed_types ) ) {
    return new WP_Error( 'invalid_file_type', 'File type not allowed' );
}

9. Third-Party API Security

Request Validation

// Validate external responses
$response = wp_remote_get( $api_url );
if ( is_wp_error( $response ) ) {
    // Handle error
    return;
}

$code = wp_remote_retrieve_response_code( $response );
if ( $code !== 200 ) {
    // Log and handle
    return;
}

$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
    // Invalid JSON
    return;
}

Timeout Configuration

$response = wp_remote_post( $api_url, [
    'timeout' => 30, // Prevent hanging
    'sslverify' => true, // Always verify SSL
    'headers' => [
        'Authorization' => 'Bearer ' . $api_key,
    ],
]);

10. Security Checklist

Before deployment:


11. Reporting Vulnerabilities

Found a security issue? Report responsibly:

  1. Email: security@leowebmarketing.com
  2. Do not disclose publicly until patched
  3. Include steps to reproduce
  4. We aim to respond within 48 hours