Security Hardening
Data Validation and Sanitization
Security in WordPress follows the principle of "Sanitize on Input, Escape on Output." All data received from users, APIs, or the database must be treated as untrusted.
Input Sanitization
When receiving data via $_POST, $_GET, or $_REQUEST, use the appropriate WordPress sanitization functions to clean the data before processing or saving it to the database.
// Sanitizing a standard text input
$user_bio = sanitize_text_field( $_POST['user_bio'] );
// Sanitizing an email address
$user_email = sanitize_email( $_POST['user_email'] );
// Ensuring a value is a non-negative integer
$post_id = absint( $_GET['p'] );
// Sanitizing a URL
$external_link = esc_url_raw( $_POST['website_url'] );
Data Validation
Validation should occur before any logic is processed. Use validation to ensure the data matches the expected format.
// Check if a value is a valid email
if ( ! is_email( $_POST['email'] ) ) {
wp_die( 'Invalid email address provided.' );
}
// Check if a value exists in an allowed list (Whitelisting)
$allowed_priorities = ['low', 'medium', 'high'];
if ( ! in_array( $_POST['priority'], $allowed_priorities, true ) ) {
$priority = 'low'; // Default fallback
}
Secure Output (Escaping)
To prevent Cross-Site Scripting (XSS) attacks, all dynamic data must be escaped at the moment it is rendered in the HTML. This is known as Late Escaping.
| Function | Use Case |
| :--- | :--- |
| esc_html() | Use when data is placed inside an HTML element (e.g., <div>$data</div>). |
| esc_attr() | Use when data is placed inside an HTML attribute (e.g., value="$data"). |
| esc_url() | Use for all URLs, including those in src or href attributes. |
| esc_js() | Use for inline Javascript strings. |
| wp_kses() | Use for content that contains allowed HTML tags (retains specific tags). |
Example Usage:
<div class="user-profile">
<h2><?php echo esc_html( $user->display_name ); ?></h2>
<a href="<?php echo esc_url( $user->website ); ?>">Visit Website</a>
<input type="text" value="<?php echo esc_attr( $user->job_title ); ?>" />
</div>
Preventing CSRF with Nonces
Nonces ("numbers used once") protect against Cross-Site Request Forgery (CSRF). They ensure that the user intended to perform a specific action, such as deleting a post or saving settings.
1. Generating a Nonce
Add a nonce field to your forms or append it to a URL.
// In a form
wp_nonce_field( 'save_profile_action', 'profile_nonce_field' );
// In a URL
$url = wp_nonce_url( admin_url( 'admin.php?page=my-plugin' ), 'delete_item_action', 'my_nonce' );
2. Verifying a Nonce
Always verify the nonce on the server side before executing the requested action.
public function handle_form_submission() {
if ( ! isset( $_POST['profile_nonce_field'] ) || ! wp_verify_nonce( $_POST['profile_nonce_field'], 'save_profile_action' ) ) {
wp_die( 'Security check failed.' );
}
// Process form data...
}
Database Security
To prevent SQL Injection, never pass raw variables into a SQL query. Use the $wpdb->prepare() method to safely format queries.
Using $wpdb->prepare
The prepare method uses placeholders (%s for string, %d for integer, %f for float) to handle data safely.
global $wpdb;
$user_id = 123;
$status = 'active';
// Secure query using prepare
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}my_table WHERE user_id = %d AND status = %s",
$user_id,
$status
)
);
Permission and Authorization
Always check if the current user has the authority to perform an action using current_user_can().
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die( 'You do not have permission to access this page.' );
}
Common capabilities include:
manage_options(Administrators)edit_posts(Authors/Editors)read(All roles)