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:
- All user input sanitized
- All output escaped
- Nonces verified on all forms/AJAX
- Capability checks on admin endpoints
- Prepared statements for all DB queries
- API keys not exposed to frontend
- Rate limiting enabled
- Debug mode disabled
- Error messages don't leak sensitive info
- SSL enforced for API calls
11. Reporting Vulnerabilities
Found a security issue? Report responsibly:
- Email: security@leowebmarketing.com
- Do not disclose publicly until patched
- Include steps to reproduce
- We aim to respond within 48 hours