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.phpQué 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 --> HPaso 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.phpDesde 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ón | Qué señala | Patrón en esta guía |
|---|---|---|
direct_file_access | archivos PHP sin comprobación de ABSPATH | acceso directo a archivos |
late_escaping | salida al navegador sin escapar | XSS |
direct_db | valores sin escapar en consultas directas a la base de datos | inyección SQL |
direct_db_queries | consultas directas a la base de datos en general | inyección SQL |
safe_redirect | wp_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.







