Auditoría de plugins WordPress

Auditoría de plugins WordPress

Actualizado: 27 de octubre de 2026
22 min de lectura
Guía
500+ proyectos WP
Auditor de seguridad

#Riesgos del código de plugins WordPress generado por IA

El uso de modelos de lenguaje grandes (LLM) como GPT-4, Claude 3.5 Sonnet o Gemini Pro para escribir código de plugins de WordPress se ha convertido en una práctica común para desarrolladores y propietarios de sitios. La promesa es sumamente atractiva: describe la funcionalidad requerida en lenguaje natural y la IA generará un archivo PHP listo para usar en cuestión de segundos.

El problema, sin embargo, es que los modelos de lenguaje destacan en sintaxis y estructura, pero carecen de contexto operativo y de una comprensión real de los principios de seguridad dentro del ecosistema de WordPress. Entrenados en bases de datos masivas de código público, heredan millones de plugins obsoletos, inseguros y mal escritos publicados durante las últimas dos décadas. Como resultado, la IA a menudo produce código sintácticamente impecable y funcional, pero plagado de vulnerabilidades de seguridad críticas bajo el capó.

Como ingenieros de WordPress, debemos adoptar una postura estricta de “Zero Trust” hacia cualquier código escrito por una IA. Esta guía analiza los cinco patrones de fallos de seguridad más comunes que observamos durante las auditorías de código de plugins generados por IA, ilustrándolos con fragmentos de PHP vulnerables junto con refactorizaciones seguras y de nivel de producción que cumplen con los Estándares de Codificación de WordPress (WPCS).


#1. is_admin() o current_user_can() en WordPress

Quizás el error lógico más común que comete la IA al intentar proteger un endpoint es confiar en la función is_admin() para restringir el acceso a los administradores.

#Por qué is_admin() no comprueba permisos

A pesar de su nombre, is_admin() no verifica si el usuario actualmente conectado es un administrador. Solo comprueba si la solicitud actual es para una página de administración (como cualquier URL que comience con /wp-admin/).

Cada solicitud AJAX enviada a /wp-admin/admin-ajax.php, ya sea iniciada por un administrador, un suscriptor conectado con capacidades mínimas o un visitante anónimo no autenticado, hará que is_admin() devuelva true.

#is_admin() como única comprobación al guardar

// Un gancho generado por IA para guardar la configuración del plugin
add_action( 'admin_init', 'ai_save_plugin_settings' );
function ai_save_plugin_settings() {
    // La IA asume incorrectamente que esto restringe el acceso a los administradores
    if ( is_admin() ) {
        if ( isset( $_POST['my_plugin_option'] ) ) {
            update_option( 'my_plugin_option', $_POST['my_plugin_option'] );
        }
    }
}

#Comprobar permisos con current_user_can()

Para verificar los permisos reales del usuario conectado, debes usar la función current_user_can(), pasando una capacidad de usuario específica en lugar de un nombre de rol, como manage_options.

add_action( 'admin_init', 'secure_save_plugin_settings' );
function secure_save_plugin_settings() {
    // 1. Verificar las capacidades reales del usuario actual
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( esc_html__( 'No tienes permiso para ejecutar esta acción.', 'secure-plugin' ) );
    }

    if ( isset( $_POST['my_plugin_option'] ) ) {
        // Aplicar sanitización de entrada (ver Sección 4)
        $sanitized_value = sanitize_text_field( wp_unslash( $_POST['my_plugin_option'] ) );
        update_option( 'my_plugin_option', $sanitized_value );
    }
}

#2. Cómo verificar el nonce en WordPress contra CSRF

Los ataques de falsificación de solicitud en sitios cruzados (CSRF) engañan al navegador de un usuario autenticado (por ejemplo, un administrador) para que ejecute acciones no autorizadas en un sitio en el que ha iniciado sesión. WordPress utiliza tokens criptográficos llamados nonces para protegerse contra CSRF.

Los generadores de IA frecuentemente omiten la creación y verificación de nonces en formularios de administración, manejadores AJAX y endpoints de API REST personalizados. Esto ocurre porque la implementación de nonces requiere coordinar la capa de renderizado frontend (el formulario HTML que contiene el campo nonce) con el controlador de procesamiento backend, lo cual es difícil de alinear para los LLM a través de generaciones de código desconectadas.

#Manejador AJAX sin verificación de nonce

// Registro de endpoint AJAX generado por IA
add_action( 'wp_ajax_ai_delete_post', 'ai_delete_post_handler' );
function ai_delete_post_handler() {
    // La IA comprueba las capacidades pero ignora por completo CSRF (sin verificación de nonce)
    if ( ! current_user_can( 'delete_posts' ) ) {
        wp_send_json_error( 'No autorizado.' );
    }

    $post_id = intval( $_POST['post_id'] );
    wp_delete_post( $post_id );
    wp_send_json_success( 'Entrada eliminada.' );
}

#Verificar el nonce con check_ajax_referer()

El formulario de salida debe generar un campo nonce usando wp_nonce_field(), y el controlador PHP receptor debe verificar el token usando check_ajax_referer() o wp_verify_nonce().

// En el archivo de renderizado del formulario o payload de javascript:
// wp_nonce_field( 'delete_post_action', 'my_nonce_field' );

// Registro del manejador AJAX seguro
add_action( 'wp_ajax_secure_delete_post', 'secure_delete_post_handler' );
function secure_delete_post_handler() {
    // 1. Verificar el token CSRF temprano
    check_ajax_referer( 'delete_post_action', 'my_nonce_field' );

    // 2. Verificar las capacidades del usuario
    if ( ! current_user_can( 'delete_posts' ) ) {
        wp_send_json_error( 'No autorizado.' );
    }

    // 3. Validar y tipar las variables de entrada
    if ( ! isset( $_POST['post_id'] ) ) {
         wp_send_json_error( 'Falta el ID de la entrada.' );
    }

    $post_id = absint( $_POST['post_id'] );
    
    if ( wp_delete_post( $post_id ) ) {
        wp_send_json_success( 'Entrada eliminada con éxito.' );
    } else {
        wp_send_json_error( 'Error al eliminar la entrada.' );
    }
}

#De qué no protege un nonce de WordPress

Un nonce solo demuestra que la petición viene de un formulario o enlace que WordPress generó para este usuario. El capítulo sobre nonces de las API de seguridad de WordPress indica que nunca se debe confiar en los nonces para la autenticación, la autorización ni el control de acceso, y por eso la comprobación de capacidades sigue siendo obligatoria a su lado. En la revisión importan tres propiedades. El nonce se genera por usuario y por sesión, así que un token copiado de una cuenta falla en otra. La vida útil por defecto es de 24 horas, dividida en dos periodos (ticks), y WordPress acepta el periodo actual y el anterior, de modo que un token concreto funciona entre 12 y 24 horas. Además, un nonce no es de un solo uso: el mismo valor puede enviarse de nuevo dentro de esa ventana, así que no frena la repetición de peticiones (replay). Un manejador generado que trata un nonce válido como prueba de identidad tiene protección contra CSRF y, aun así, ningún control de acceso.


#3. Vulnerabilidades de inyección SQL en consultas $wpdb

Cuando un plugin interactúa directamente con la base de datos en lugar de utilizar el wrapper nativo WP_Query, utiliza el objeto global $wpdb. Desafortunadamente, los LLM con frecuencia construyen cadenas SQL sin procesar utilizando la concatenación directa de variables suministradas por el usuario ($_POST, $_GET) en lugar de parametrizar la consulta.

#Por qué es peligroso concatenar SQL

Concatenar variables sin procesar directamente en las consultas de la base de datos permite a un atacante manipular la estructura de la consulta. Esto puede llevar a la recuperación no autorizada de datos (como contraseñas de usuario hash), la eliminación de la base de datos o la escalación de privilegios administrativos.

#Consulta SQL con variable concatenada

// Función de búsqueda de usuarios generada por IA
function ai_find_users_by_city( $city_name ) {
    global $wpdb;
    // Fallo crítico: concatenación directa de la variable de consulta
    $query = "SELECT * FROM {$wpdb->prefix}custom_users WHERE city = '" . $city_name . "'";
    return $wpdb->get_results( $query );
}

#Proteger consultas con $wpdb->prepare()

Nunca se deben escribir variables sin procesar directamente en las cadenas SQL. En su lugar, utiliza el método $wpdb->prepare(), que funciona como un motor de consultas parametrizadas, tipando y escapando los valores mediante especificadores de formato (%s para cadenas, %d para enteros, %f para flotantes).

function secure_find_users_by_city( $city_name ) {
    global $wpdb;

    // 1. Usar prepare() con especificadores de formato
    $query = $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}custom_users WHERE city = %s",
        $city_name
    );

    return $wpdb->get_results( $query );
}

Nota: Si la variable $city_name proviene directamente de la entrada del usuario, aún debe ser sanitizada usando sanitize_text_field() antes del procesamiento de la base de datos.


#4. Sanitizar y escapar datos en WordPress (XSS)

Cross-Site Scripting (XSS) es una de las vulnerabilidades más comunes encontradas en los plugins de WordPress. Permite que el código JavaScript malicioso se almacene en la base de datos o se refleje dinámicamente, ejecutándose en los navegadores de visitantes desprevenidos.

Los modelos de IA a menudo confunden la sanitización (limpieza de datos al ingresar) con el escape (aseguramiento de datos al salir). Incluso si la IA sanitiza las entradas, a menudo muestra variables en las plantillas sin escaparlas primero, dejando el sitio expuesto a XSS almacenado.

#Salida de datos sin escapar

// Bucle de visualización de comentarios escrito por IA
function ai_display_user_feedback() {
    $feedbacks = get_option( 'ai_user_feedback_list', [] );
    
    echo '<div class="feedback-list">';
    foreach ( $feedbacks as $feedback ) {
        // Fallo crítico: salida sin procesar y sin escapar
        echo '<p class="feedback-item">' . $feedback['user_comment'] . '</p>';
    }
    echo '</div>';
}

#Escapar la salida con esc_html()

Sigue la regla fundamental de WordPress: Sanitize Early, Escape Late (sanitiza las variables de entrada inmediatamente; escapa la salida inmediatamente antes de enviarla al navegador).

Elige la función de escape correcta para el contexto:

  • esc_html() - para nodos de texto estándar dentro de elementos HTML.
  • esc_attr() - para la salida dentro de valores de atributos HTML.
  • esc_url() - para la salida de enlaces.
  • wp_kses() - cuando debas permitir etiquetas HTML específicas y seguras (como strong, anchors).
function secure_display_user_feedback() {
    $feedbacks = get_option( 'secure_user_feedback_list', [] );
    
    echo '<div class="feedback-list">';
    foreach ( $feedbacks as $feedback ) {
        $comment = isset( $feedback['user_comment'] ) ? $feedback['user_comment'] : '';
        
        // 1. Escapar la salida contextualmente inmediatamente antes de renderizar
        echo '<p class="feedback-item">';
        echo esc_html( $comment );
        echo '</p>';
    }
    echo '</div>';
}

#5. Subida segura de archivos en WordPress

Permitir a los usuarios cargar archivos (como archivos adjuntos de formularios de contacto o cargas de avatares) es una de las operaciones de mayor riesgo en un sitio web. Un error aquí permite a los atacantes cargar un archivo .php, lo que resulta en la Ejecución Remota de Código (RCE) y el compromiso total del servidor.

Los manejadores de archivos generados por IA frecuentemente dependen de comandos nativos de PHP como move_uploaded_file() combinados con comprobaciones débiles de extensión de archivo (como una simple coincidencia de cadenas), que son triviales de eludir para los atacantes (por ejemplo, cargar archivos llamados malicious.php.jpg o usar extensiones alternativas como .phtml).

#Subida con move_uploaded_file() y comprobación de extensión

// Manejador de carga escrito por IA
function ai_handle_avatar_upload() {
    if ( isset( $_FILES['avatar'] ) ) {
        $file_name = $_FILES['avatar']['name'];
        $ext = pathinfo( $file_name, PATHINFO_EXTENSION );
        
        // La IA asume que esta comprobación de extensión es segura
        if ( in_array( $ext, ['jpg', 'jpeg', 'png'] ) ) {
            $target = wp_upload_dir()['path'] . '/' . $file_name;
            move_uploaded_file( $_FILES['avatar']['tmp_name'], $target );
        }
    }
}

#Subida segura con wp_handle_upload()

Nunca uses move_uploaded_file() nativo de PHP dentro de WordPress. En su lugar, utiliza la función core wp_handle_upload(). Realiza una comprobación robusta del tipo MIME (verificando la firma real del archivo, no solo su nombre), valida el estado de la carga y se integra con el sistema de permisos de WordPress.

function secure_handle_avatar_upload() {
    // 1. Validar el token CSRF
    if ( ! isset( $_POST['avatar_upload_nonce'] ) || ! wp_verify_nonce( $_POST['avatar_upload_nonce'], 'upload_avatar_action' ) ) {
        wp_die( esc_html__( 'Token de seguridad no válido.', 'secure-plugin' ) );
    }

    // 2. Verificar las comprobaciones de capacidades
    if ( ! current_user_can( 'upload_files' ) ) {
        wp_die( esc_html__( 'No tienes permiso para cargar archivos.', 'secure-plugin' ) );
    }

    if ( isset( $_FILES['avatar'] ) ) {
        if ( ! function_exists( 'wp_handle_upload' ) ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        // 3. Restringir a la fuerza los tipos MIME permitidos
        $allowed_mimes = [
            'jpg|jpeg' => 'image/jpeg',
            'png'      => 'image/png'
        ];

        $upload_overrides = [
            'test_form' => false,
            'mimes'     => $allowed_mimes
        ];

        // 4. Delegar el procesamiento de la carga a la función core
        $movefile = wp_handle_upload( $_FILES['avatar'], $upload_overrides );

        if ( $movefile && ! isset( $movefile['error'] ) ) {
            $avatar_url = $movefile['url'];
            update_user_meta( get_current_user_id(), 'user_avatar', $avatar_url );
        } else {
            // Registrar el fallo de forma segura
            error_log( 'Fallo en la carga de archivos: ' . $movefile['error'] );
        }
    }
}

#6. Funciones de WordPress inventadas y paquetes de Composer falsos

Hay una clase de error exclusiva de los modelos generativos: inventar con total seguridad código que no existe. Como un LLM predice tokens plausibles en lugar de consultar la referencia real de WordPress, llama sin problema a funciones que nunca formaron parte del núcleo, como wp_verify_admin(), get_current_user_role() o sanitize_all_input(). Estos nombres parecen creíbles porque siguen las convenciones de nombres de WordPress, y a menudo el primer aviso es un error fatal en ejecución. Peor aún: un desarrollador con prisa “arregla” el error fatal escribiendo su propio envoltorio mínimo con el nombre inventado y reintroduce justo el hueco de saneamiento o de permisos que la función inventada prometía cerrar.

El mismo patrón afecta a las dependencias de terceros. Cuando la IA genera un composer.json o propone líneas require, puede citar paquetes que no existen en Packagist o, peor, paquetes cuyo nombre difiere en un solo carácter de una biblioteca legítima. Los atacantes explotan este “slopsquatting” registrando antes los nombres alucinados, de modo que un composer install sin revisar mete código malicioso directamente en el plugin.

Dos comprobaciones concretas cierran este hueco. Primero, contrasta cada función desconocida con la referencia oficial de código de WordPress antes de confiar en ella; si no hay entrada, trata la llamada como inventada. Segundo, fija y revisa cada dependencia: verifica que el paquete existe exactamente con ese nombre, que viene del mantenedor esperado y que está fijado a una versión revisada en lugar de a un rango abierto.

// Alucinada: esta función no existe en el núcleo
if ( wp_current_user_is_admin() ) { /* ... */ }

// API real de WordPress
if ( current_user_can( 'manage_options' ) ) { /* ... */ }

wp plugin verify-checksums compara los archivos del plugin con las sumas de verificación publicadas en WordPress.org (documentación de WP-CLI), así que solo sirve para plugins del directorio, no para un plugin escrito por la IA. Para las dependencias de Composer, el control es un composer.lock fijado y revisado en el diff, y composer why <paquete> muestra qué requisito trajo un paquete inesperado.


#7. Cómo bloquear el acceso directo a los archivos PHP del plugin

Cada archivo PHP de un plugin o tema debe negarse a ejecutarse si WordPress no se ha cargado. El manual de plugins recomienda para ello comprobar la constante ABSPATH al principio del archivo. El código generado omite a menudo esa línea, sobre todo en archivos auxiliares que el modelo separó para que el código fuera más legible.

#Archivo auxiliar sin comprobación de ABSPATH

<?php
function fjord_get_export_rows() {
    global $wpdb;
    return $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}fjord_records" );
}

Una petición directa a /wp-content/plugins/fjord-exporter/helpers.php puede ejecutar el archivo fuera de WordPress y, según la configuración del servidor, saltarse por completo el inicio de sesión.

#Comprobación de ABSPATH en los archivos PHP del plugin

<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

En la revisión, la regla es sencilla: cada archivo .php del directorio del plugin necesita estas líneas, incluidos uninstall.php y las copias de bibliotecas. También son sospechosos los archivos “API” generados que cargan WordPress por su cuenta con require 'wp-load.php'; y después no comprueban la autenticación.


#8. Rutas de la REST API sin permission_callback

Los plugins generados exponen datos a menudo con register_rest_route() en lugar de admin-ajax.php. La decisión de acceso de una ruta REST está en un único argumento, permission_callback, y los modelos lo estropean de dos formas: lo omiten o lo ponen en __return_true para que desaparezca un error 401 durante las pruebas.

El manual de la REST API cubre ambos casos. Desde WordPress 5.5, una ruta registrada sin permission_callback lanza un aviso _doing_it_wrong con el nombre de la ruta, así que el callback que falta aparece en el registro de depuración de cualquier sitio con WP_DEBUG activado. __return_true es el valor documentado para los endpoints que deben ser públicos. Con él no aparece ningún aviso, de modo que una ruta de escritura marcada como pública por error pasa desapercibida.

#Ruta de escritura abierta a cualquiera

register_rest_route( 'fjord/v1', '/settings', array(
    'methods'             => 'POST',
    'callback'            => 'fjord_update_settings',
    'permission_callback' => '__return_true',
) );

#Ruta de escritura con comprobación de capacidades y argumentos validados

register_rest_route( 'fjord/v1', '/settings', array(
    'methods'             => WP_REST_Server::EDITABLE,
    'callback'            => 'fjord_update_settings',
    'permission_callback' => function () {
        return current_user_can( 'manage_options' );
    },
    'args'                => array(
        'label' => array(
            'type'              => 'string',
            'required'          => true,
            'validate_callback' => function ( $value ) {
                return is_string( $value ) && strlen( $value ) <= 100;
            },
            'sanitize_callback' => 'sanitize_text_field',
        ),
    ),
) );

Para cada argumento, validate_callback devuelve true o false y el servidor REST rechaza los datos no válidos con un error que el cliente puede leer, mientras que sanitize_callback limpia el valor antes de que llegue a tu callback. En la revisión, haz una lista de cada llamada a register_rest_route() y lee primero su permission_callback: un __return_true en una ruta que acepta algo más que GET necesita una justificación por escrito. Para las peticiones del navegador autenticadas con cookie, el capítulo de autenticación añade otra regla: sin un nonce wp_rest en la cabecera X-WP-Nonce, la API fija el usuario actual en 0. Un script de frontend que “funciona” solo porque se salta el nonce está llamando a la ruta, en realidad, como un visitante sin sesión.


#Auditoría del código de plugins WordPress generado por IA

Para implementar de forma segura el código generado por IA, tu equipo de ingeniería debe establecer un proceso de revisión sistemático. Implementa estos tres pasos antes de enviar cualquier activo generado a producción:

#Paso 1: Automatizar el análisis estático (PHPCS)

Configura los Estándares de Codificación de WordPress (WPCS) para la herramienta PHP_CodeSniffer. Escanea archivos e identifica automáticamente la falta de sanitización, los nonces omitidos y las variables sin escapar.

# Escanear un archivo de plugin generado usando los estándares de WordPress
vendor/bin/phpcs --standard=WordPress-Extra path/to/generated-plugin.php

#Qué reglas de WPCS detectan estos fallos de seguridad

WordPress Coding Standards 3.x solo se instala con Composer y necesita PHP 7.2 o superior; el repositorio de WordPressCS publicó la versión 3.4.1 en julio de 2026.

composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev wp-coding-standards/wpcs:"^3.0"

WordPress-Extra contiene WordPress-Core más prácticas recomendadas. Las reglas (sniffs) de seguridad están en dos grupos. WordPress.Security.NonceVerification señala lecturas de $_POST y $_GET sin una comprobación de nonce previa, WordPress.Security.ValidatedSanitizedInput señala variables superglobales usadas sin isset(), wp_unslash() y una función de saneamiento, WordPress.Security.EscapeOutput señala salida sin escapar y WordPress.Security.SafeRedirect señala wp_redirect() donde corresponde una redirección segura. En la parte de base de datos, WordPress.DB.PreparedSQL y WordPress.DB.PreparedSQLPlaceholders señalan SQL con variables interpoladas y errores de marcadores, y WordPress.DB.DirectDatabaseQuery marca las consultas directas para revisarlas. Así se ejecutan solo las reglas de seguridad sobre un plugin generado:

vendor/bin/phpcs --standard=WordPress --sniffs=WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput,WordPress.Security.EscapeOutput,WordPress.DB.PreparedSQL path/to/generated-plugin/

Ninguna regla sabe si un manejador exige la capacidad correcta, así que la revisión de current_user_can() del paso 2 sigue siendo manual.

#Paso 2: Realizar una revisión de código manual

Revisa cada manejador que ejecute consultas HTTP, transacciones de base de datos, llamadas AJAX o conexiones de API REST según el siguiente diagrama de flujo:

graph TD
    A["Recibir Código de Plugin Escrito por IA"] --> B{"¿Acepta entrada del usuario?"}
    B -- Sí --> C["Comprobar nonce y capabilities"]
    B -- No --> D{"¿Modifica la base de datos?"}
    
    C --> C1["Implementar check_ajax_referer() / validación CSRF"]
    C --> C2["Implementar current_user_can() comprobación de permisos"]
    C1 --> D
    C2 --> D
    
    D -- Sí --> E["Comprobar uso de $wpdb->prepare()"]
    D -- No --> F{"¿Envía salida al navegador?"}
    
    E --> E1["Implementar variables SQL parametrizadas"]
    E1 --> F
    
    F -- Sí --> G["Verificar esc_html(), esc_attr(), o esc_url()"]
    F -- No --> H["Proceder a las pruebas de integración"]
    
    G --> G1["Implementar escape de salida contextual"]
    G1 --> H

#Paso 3: Ejecutar pruebas con bajos privilegios

Verifica los límites de acceso del código. Inicia sesión como un usuario con el rol de “Suscriptor” y activa las acciones REST o AJAX del plugin directamente a través de curl o Postman. Si el servidor ejecuta modificaciones o revela datos de la base de datos, faltan las capas de permisos.

Si un agente de IA hace parte de esta revisión, el paquete security-audit de Cloudflare, publicado con licencia MIT (README leído en octubre de 2026), muestra cómo mantener fiables sus hallazgos. Cada sospecha pasa a un agente verificador nuevo que intenta refutarla, de modo que el agente que comprueba un hallazgo nunca es el que lo encontró. Una pista que no se puede demostrar queda marcada como needs_validation, sin gravedad asignada, y no entra en el informe como vulnerabilidad. La falta de una segunda capa de defensa, cuando la primera ya bloquea el ataque, cuenta como nota de refuerzo. Cloudflare añade que, en sus propias pruebas, una sola ejecución encontró aproximadamente la mitad de las vulnerabilidades que hallaron en total las ejecuciones repetidas. En un plugin generado, una pasada de IA es por tanto un punto de partida; PHPCS, la lista de comprobación para cada manejador y las pruebas con bajos privilegios siguen siendo necesarias.

Actualización del . Dos comprobaciones más pertenecen a esta lista, y los plugins para WooCommerce necesitan ambas. La primera trata de mostrar una entrada o un producto por su ID. Toda función que reciba un ID de entrada o producto de una petición o de un atributo de shortcode tiene que comprobar estado, visibilidad y contraseña antes de imprimir nada; si no, un borrador o un producto privado o con contraseña mostrará su nombre, precio o imagen a través de un shortcode, una vista rápida o una lista de comparación. Busca en el PHP get_post(, wc_get_product( y shortcode_atts(, sigue cada ID hasta la salida y localiza una comprobación de estado y visibilidad (como current_user_can( 'read_post', $id ) o $product->is_visible()) además de post_password_required(). La segunda está en el JavaScript. WooCommerce lanza sus eventos de variación y carrito a través de jQuery, así que un addEventListener nativo sobre nombres como found_variation o updated_cart_totals nunca se activa. Busca esos nombres en el JavaScript y comprueba que los escuchadores usan jQuery, por ejemplo jQuery( document.body ).on( 'updated_cart_totals', ... ).


#Cómo ejecutar Plugin Check en un plugin generado por IA

Plugin Check es la herramienta con la que WordPress.org comprueba los plugins frente a los requisitos del directorio, y resulta igual de útil en un plugin privado que nunca se va a enviar. La versión 2.1.0 salió en agosto de 2026. Funciona en dos modos: comprobaciones estáticas que leen el código, en parte con reglas de PHP_CodeSniffer y en parte con reglas propias, y comprobaciones en tiempo de ejecución que activan el plugin y ejecutan partes de él. El proyecto desaconseja usarlo en un sitio de producción, así que instálalo en staging.

wp plugin check fjord-exporter
wp plugin check fjord-exporter --categories=security --format=csv --fields=code,type,severity,message
wp plugin check fjord-exporter --require=./wp-content/plugins/plugin-check/cli.php

Desde WP-CLI solo se ejecutan por defecto las comprobaciones estáticas; el tercer comando carga cli.php para que también se ejecuten las de tiempo de ejecución. La lista de comprobaciones asigna cinco a la categoría security, y coinciden con los patrones de esta guía:

ComprobaciónQué señalaPatrón en esta guía
direct_file_accessarchivos PHP sin comprobación de ABSPATHacceso directo a archivos
late_escapingsalida al navegador sin escaparXSS
direct_dbvalores sin escapar en consultas directas a la base de datosinyección SQL
direct_db_queriesconsultas directas a la base de datos en generalinyección SQL
safe_redirectwp_redirect() en lugar de wp_safe_redirect()redirección abierta

El conjunto de reglas PHPCS de la comprobación plugin_review_phpcs carga además las reglas de WPCS para nonces, saneamiento de entradas y SQL preparado, pero pertenece a la categoría plugin_repo, así que --categories=security oculta esos resultados. Una comprobación más afecta en concreto al código generado: file_type señala directorios de herramientas de IA como .cursor, .claude, .aider, .continue, .windsurf y .ai que se quedaron dentro del plugin porque su carpeta era el directorio de trabajo de un asistente de programación. Cada resultado tiene un tipo (ERROR o WARNING) y una gravedad de 1 a 10. Plugin Check no sabe qué capacidad debería exigir un manejador, así que acorta la revisión manual sin sustituirla.

#Cuándo parchear un plugin hecho con IA y cuándo rescatar toda la web

Los fallos localizados (un handler AJAX, un formulario de administración) merecen un parche después de la revisión. Cuando los mismos cinco patrones aparecen en varios archivos generados, cuando hay funciones inventadas conectadas a rutas de producción o cuando los plugins elegidos por la IA arrastran CVE activos además de código propio deficiente, deja de parchear por separado. La auditoría de rescate de webs hechas por IA entrega una división explícita entre conservar, reescribir y eliminar, para no pagar una reconstrucción completa cuando basta un rescate ni parchear sin fin cuando la base no se sostiene. Si además el stack carga más de treinta plugins y unas Core Web Vitals rotas, combina la auditoría de código con el rescate de Core Web Vitals para webs hechas con IA.

El precio del rescate y de la auditoría es individual y se fija después de ese inventario. Escríbenos con la URL del plugin o de la web y cómo se generó el código.


#Resumen y recomendaciones

La inteligencia artificial es un poderoso acelerador para escribir código, pero no puede asumir la responsabilidad de la ingeniería. Trata todo el código escrito por la IA como si hubiera sido redactado por un becario sin experiencia.

Asegúrate de implementar comprobaciones de capacidades robustas (current_user_can()), verificación de CSRF (wp_verify_nonce()), parametrización de bases de datos ($wpdb->prepare()), sanitización de entradas y escape tardío de salidas. Al combinar estas prácticas principales con herramientas de análisis estático, puedes escribir código de WordPress más rápido mientras mantienes una aplicación completamente segura.

Siguiente paso

Transforma el artículo en una implementación real

Este bloque refuerza el enlazado interno y lleva al lector al siguiente paso más útil dentro de la arquitectura del sitio.

Explora otros servicios WordPress y base de conocimiento

Refuerza tu negocio con soporte técnico profesional en áreas clave del ecosistema WordPress.

Preguntas frecuentes

Respuestas prácticas para aplicar el tema en la ejecución real.

Por qué suele ser inseguro el código de los plugins de WordPress generado por IA#
Los generadores de IA (LLM) se entrenan con código de acceso público, del cual gran parte contiene patrones obsoletos o inseguros de los últimos 15 años. Además, la IA carece de contexto operativo de WordPress, produciendo código PHP sintácticamente correcto que omite por completo comprobaciones de capacidades y de CSRF (nonces).
Cuál es la diferencia entre is_admin() y current_user_can() para la seguridad#
La función is_admin() solo verifica si la solicitud actual corresponde a una página de administración (como /wp-admin/) y no comprueba las capacidades del usuario. Su uso para control de acceso permite a cualquier usuario autenticado (incluidos suscriptores) ejecutar el código. El control seguro requiere usar current_user_can('manage_options') o similar.
Cómo introduce la IA vulnerabilidades de inyección SQL en el código de WordPress#
El problema más común es la concatenación directa de variables de entrada del usuario ($_POST o $_GET) dentro de cadenas de consulta SQL, ej. $wpdb->query("SELECT * FROM table WHERE id = $id"). Para prevenir la inyección SQL, las consultas deben prepararse usando la función $wpdb->prepare() con sus especificadores de formato (%d para enteros, %s para cadenas).
Qué son la sanitización y el escape en los estándares de codificación de WordPress#
La sanitización (como sanitize_text_field) limpia los datos de entrada antes de guardarlos en la base de datos. El escape (como esc_html, esc_attr, esc_url) asegura los datos de salida inmediatamente antes de renderizarlos en el navegador, previniendo ataques de Cross-Site Scripting (XSS). El código de IA confunde a menudo estas prácticas o las omite.

¿Necesitas un FAQ adaptado a tu sector y mercado? Preparo una versión alineada con tus objetivos de negocio.

Hablemos

Artículos Relacionados

PHP2Shell: ¿está en riesgo mi sitio WordPress y qué hacer?

PHP 8.5.12, 8.4.27, 8.3.36 y 8.2.35, publicadas el 21 de octubre de 2026, corrigen dos vulnerabilidades críticas en el propio PHP (CVE-2026-109618, CVE-2026-109619). Cómo comprobar la versión de PHP en WordPress, qué hacer si el hosting aún no ha actualizado el servidor y qué contraseñas cambiar después de la actualización.