WordPress Security for Developers: Building Secure Code
Essential security practices for WordPress developers including input validation, output escaping, and secure coding patterns.
Developers play a critical role in WordPress security. Understanding secure coding practices prevents vulnerabilities in themes and plugins that could compromise thousands of sites.
Security Mindset for Developers
Core Principles
- Never trust user input
- Always validate and sanitize
- Escape output based on context
- Use WordPress APIs when available
- Follow principle of least privilege
Input Validation
Sanitization Functions
// Text input
$title = sanitize_text_field($_POST['title']);
// Textarea content
$content = sanitize_textarea_field($_POST['content']);
// Email addresses
$email = sanitize_email($_POST['email']);
// URLs
$url = esc_url_raw($_POST['url']);
// File names
$file = sanitize_file_name($_POST['filename']);
// HTML content (allows safe HTML)
$html = wp_kses_post($_POST['html_content']);
// Integer values
$id = absint($_GET['id']);
Custom Validation
// Validate against allowed values
function wpfs_validate_status($status) {
$allowed = array('draft', 'pending', 'publish');
return in_array($status, $allowed) ? $status : 'draft';
}
// Validate with regex
function wpfs_validate_phone($phone) {
$phone = preg_replace('/[^0-9]/', '', $phone);
return strlen($phone) >= 10 ? $phone : false;
}
Output Escaping
Context-Specific Escaping
// HTML context
echo esc_html($user_input);
// HTML attributes
echo '<div class="' . esc_attr($class) . '">';
// URLs
echo '<a href="' . esc_url($link) . '">';
// JavaScript
echo '<script>var data = ' . wp_json_encode($data) . ';</script>';
// Textarea
echo '<textarea>' . esc_textarea($content) . '</textarea>';
// Translation with escaping
echo esc_html__( 'Welcome', 'my-plugin' );
echo esc_attr__( 'Button Label', 'my-plugin' );
Database Security
Always Use $wpdb->prepare()
global $wpdb;
// Single value
$user = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->users} WHERE ID = %d",
$user_id
)
);
// Multiple values
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts}
WHERE post_type = %s AND post_status = %s",
$post_type,
$status
)
);
// LIKE queries
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_title LIKE %s",
'%' . $wpdb->esc_like($search) . '%'
);
Nonce Verification
Form Protection
// Generate nonce field
wp_nonce_field('my_action', 'my_nonce');
// Verify in handler
if (!wp_verify_nonce($_POST['my_nonce'], 'my_action')) {
wp_die('Security check failed');
}
// AJAX nonce
// In PHP:
wp_localize_script('my-script', 'myAjax', array(
'nonce' => wp_create_nonce('my_ajax_nonce')
));
// In JavaScript:
jQuery.post(ajaxurl, {
action: 'my_action',
security: myAjax.nonce
});
// Verify in PHP:
check_ajax_referer('my_ajax_nonce', 'security');
Capability Checks
Verify User Permissions
// Check specific capability
if (!current_user_can('edit_posts')) {
wp_die('Unauthorized access');
}
// Check post-specific capability
if (!current_user_can('edit_post', $post_id)) {
wp_die('You cannot edit this post');
}
// Admin-only actions
if (!current_user_can('manage_options')) {
return;
}
File Security
Safe File Operations
// Use WordPress filesystem API
global $wp_filesystem;
WP_Filesystem();
// Read file safely
$content = $wp_filesystem->get_contents($file_path);
// Write file safely
$wp_filesystem->put_contents($file_path, $content, FS_CHMOD_FILE);
// Validate file paths
$upload_dir = wp_upload_dir();
$safe_path = realpath($requested_path);
if (strpos($safe_path, $upload_dir['basedir']) !== 0) {
wp_die('Invalid path');
}
AJAX Security Pattern
Complete Secure AJAX Handler
// Register AJAX handlers
add_action('wp_ajax_my_secure_action', 'handle_my_action');
function handle_my_action() {
// 1. Verify nonce
check_ajax_referer('my_action_nonce', 'security');
// 2. Verify capabilities
if (!current_user_can('edit_posts')) {
wp_send_json_error('Unauthorized', 403);
}
// 3. Validate and sanitize input
$post_id = absint($_POST['post_id']);
$title = sanitize_text_field($_POST['title']);
if (empty($post_id) || empty($title)) {
wp_send_json_error('Invalid input');
}
// 4. Perform action
$result = wp_update_post(array(
'ID' => $post_id,
'post_title' => $title
));
// 5. Return response
if ($result) {
wp_send_json_success(array('id' => $result));
} else {
wp_send_json_error('Update failed');
}
}
Security Checklist
- All user input validated and sanitized
- All output escaped for context
- All database queries use prepare()
- All forms have nonce verification
- All actions check user capabilities
- No direct file access without validation
- Error messages don't leak sensitive info
Conclusion
Secure WordPress development requires consistent application of validation, sanitization, escaping, and authorization checks. Make these practices automatic in your development workflow.
Written by Sarah Chen
WP Folder Shield Team