title: 'Seguridad del núcleo: Nonces, Sanitization, Late Escaping y filtrado con wp_kses' description: >- Análisis exhaustivo de ingeniería sobre la canalización de seguridad en WordPress 7.0+ (PHP 8.4+): mecanismo matemático de los tokens Nonce (wp_nonce_tick), desinfección contextual de entradas, Late Escaping en el punto de salida y filtrado HTML de alto rendimiento mediante wp_kses. module: security order: 1 difficulty: advanced estimatedTime: 40 min topics:
La seguridad del backend monolítico de WordPress y sus interfaces REST se sustenta en la Tríada de Defensa en Profundidad (Defense-in-Depth):
$_POST, $_GET, REST payload) antes de persistirla en la base de datos.echo), neutralizando de raíz ataques de Cross-Site Scripting (XSS) reflejados o almacenados.Un error conceptual muy extendido es concebir los Nonces de WordPress como "números criptográficos de un solo uso" (Number Used Once). En la arquitectura del núcleo de WordPress, un nonce es un token hash vinculado a la sesión con una ventana temporal deslizante de 12 a 24 horas, calculado a partir del ID de usuario, el token de sesión y la acción específica. No previene ataques de repetición (Replay Attacks); su cometido estricto es bloquear la falsificación de peticiones en sitios cruzados (CSRF).
El segundo principio innegociable es el Late Escaping (escape en el último momento). Los datos en la base de datos (wp_posts, wp_postmeta) deben almacenarse en formato canónico y sin escapar. Las funciones de escape como esc_html(), esc_attr(), esc_url() o la parametrización granular de wp_kses() deben ejecutarse exactamente en el punto de renderizado (echo). Esto garantiza que si un plugin o una consulta directa en SQL altera la base de datos, el navegador del cliente seguirá recibiendo texto plano neutralizado.
Imagine la canalización de seguridad de WordPress como el control de acceso de una planta química de alta seguridad:
wp_verify_nonce / check_admin_referer): si un mensajero intenta entregar una orden sin el pase vigente del día (un ataque CSRF desde una pestaña externa), el acceso se bloquea de inmediato.sanitize_text_field, absint) antes de almacenarse en el depósito (MySQL).esc_html) para que el operario no sufra quemaduras al abrirlo (el navegador interpreta texto plano en lugar de ejecutar <script>).DATOS ENTRANTES (HTTP POST / REST)
│
▼
[1. VALIDACIÓN CSRF]: wp_verify_nonce($nonce, 'action')
├── NO VÁLIDO ──► wp_die('Security Check Failed', 403)
└── VÁLIDO
│
▼
[2. SANITIZATION]: sanitize_text_field(), absint(), sanitize_email()
│
▼
[3. ALMACÉN]: Datos canónicos en MySQL (wp_postmeta, wp_options)
│
▼
[4. PUNTO DE SALIDA (LATE ESCAPING)]:
├── En texto de elemento ─────► esc_html($val)
├── En atributo de etiqueta ──► esc_attr($val)
├── En enlace / URL ──────────► esc_url($val)
└── En HTML enriquecido ──────► wp_kses($val, $allowed)wp_create_nonce() calcula el hash de wp_nonce_tick() (intervalos de 12 horas), UID de usuario, session_token y sales del sitio vía wp_hash().
El manejador valida el token mediante check_admin_referer() para formularios POST, o check_ajax_referer() / rest_cookie_check_errors() para APIs.
Los parámetros se sanean con coerción de tipos: enteros con absint(), cadenas con sanitize_text_field() (elimina bytes nulos, etiquetas y espacios).
Los registros se guardan en la base de datos sin codificar entidades HTML (&, "), preservando datos canónicos para búsquedas y exportaciones.
Inmediatamente antes de cada sentencia echo, las variables pasan por esc_html/esc_attr o se compilan mediante el esquema seguro de wp_kses().
En el ecosistema de ingeniería de Fullstack Agentic Lab, la tríada de defensa se aplica para:
X-WP-Nonce o cookies de sesión.wp_kses() para especificaciones de arquitectura, reseñas y perfiles de ingenieros.WordPress-Extra de PHP_CodeSniffer, vetando el uso de echo $variable sin esc_*.sanitize_callback tipados en register_setting() y register_post_meta().Analicemos la evolución de las prácticas de seguridad de datos en WordPress:
A continuación se presenta un artefacto de producción diseñado para WordPress 7.0+ y PHP 8.4+, que implementa una pasarela segura de especificaciones arquitectónicas con verificación de Nonce, desinfección tipada, esquema wp_kses en caché y Late Escaping estricto.
<?php
/**
* Plugin Name: Fullstack Agentic Lab — Enterprise Security Gateway
* Description: Tríada de seguridad: Verificación Nonce, Input Sanitization y Late Escaping.
* Version: 3.0.0
* Requires at least: 6.8
* Requires PHP: 8.4
* Author: Artur Yusupov (Fullstack Agentic Lab)
*/
declare(strict_types=1);
namespace AgenticLab\Security;
use WP_Error;
defined('ABSPATH') || exit;
final readonly class SecureSubmissionGateway
{
private const string NONCE_ACTION = 'agentic_lab_submit_spec_v1';
private const string NONCE_FIELD = '_agentic_sec_token';
public function registerHooks(): void
{
add_action('admin_post_agentic_submit_spec', $this->handleFormSubmission(...));
}
/**
* Procesa envíos POST entrantes a través de toda la canalización de seguridad.
*/
public function handleFormSubmission(): void
{
// 1. NIVEL 1 (CSRF): Verificación del token Nonce
// check_admin_referer detiene la ejecución con wp_die() si el token no es válido
check_admin_referer(self::NONCE_ACTION, self::NONCE_FIELD);
// 2. NIVEL 2 (RBAC): Verificación de capacidades del usuario
if (!current_user_can('edit_posts')) {
wp_die(
esc_html__('Privilegios insuficientes para ejecutar esta operación.', 'agentic-lab'),
esc_html__('Error de autorización', 'agentic-lab'),
['response' => 403]
);
}
// 3. NIVEL 3 (SANITIZATION): Desinfección profunda y coerción de tipos
$rawTitle = (string) ($_POST['spec_title'] ?? '');
$rawEmail = (string) ($_POST['contact_email'] ?? '');
$rawTargetId = $_POST['target_cluster_id'] ?? 0;
$rawHtmlBody = (string) ($_POST['spec_architecture_html'] ?? '');
// Sanear cada variable estrictamente de acuerdo con su tipo semántico
$cleanTitle = sanitize_text_field($rawTitle);
$cleanEmail = sanitize_email($rawEmail);
$cleanTargetId = absint($rawTargetId);
// Filtrar HTML enriquecido con un esquema explícito de wp_kses
$cleanHtmlBody = wp_kses($rawHtmlBody, $this->getAllowedHtmlSchema());
if ($cleanTitle === '' || $cleanEmail === '' || $cleanTargetId === 0) {
wp_die(
esc_html__('Parámetros de solicitud no válidos.', 'agentic-lab'),
esc_html__('Error de validación', 'agentic-lab'),
['response' => 400]
);
}
// Persistir datos limpios y canónicos en MySQL
$recordId = wp_insert_post([
'post_title' => $cleanTitle,
'post_content' => $cleanHtmlBody,
'post_status' => 'pending',
'post_type' => 'spec_submission',
'meta_input' => [
'_agentic_contact_email' => $cleanEmail,
'_agentic_cluster_id' => $cleanTargetId,
'_agentic_submitted_at' => gmdate('c'),
],
]);
if (is_wp_error($recordId)) {
wp_die(esc_html($recordId->get_error_message()), 500);
}
// Redirección segura a la vista de confirmación
wp_safe_redirect(add_query_arg('status', 'submitted', wp_get_referer() ?: admin_url()));
exit;
}
/**
* Esquema de lista blanca de HTML permitido.
* Prohíbe <script>, <iframe>, <style>, <svg>, <object> y eventos en línea on*.
*
* @return array<string, array<string, bool>>
*/
private function getAllowedHtmlSchema(): array
{
return [
'h3' => ['class' => true],
'h4' => ['class' => true],
'p' => ['class' => true],
'strong' => [],
'em' => [],
'code' => ['class' => true],
'pre' => ['class' => true],
'ul' => ['class' => true],
'ol' => ['class' => true],
'li' => ['class' => true],
'a' => [
'href' => true,
'title' => true,
'target' => true,
'rel' => true,
'class' => true,
],
];
}
/**
* 4. NIVEL 4 (LATE ESCAPING): Renderizado contextual seguro en la vista.
*/
public static function renderSubmissionCard(int $submissionId): void
{
$submission = get_post($submissionId);
if (!$submission) {
return;
}
$email = (string) get_post_meta($submissionId, '_agentic_contact_email', true);
$clusterId = (int) get_post_meta($submissionId, '_agentic_cluster_id', true);
$statusUrl = admin_url("admin.php?page=cluster-view&id={$clusterId}");
// CRÍTICO: ¡Escapar variables en el punto exacto de la llamada a echo según su contexto HTML!
?>
<article class="agentic-spec-card" id="submission-<?php echo esc_attr((string) $submissionId); ?>">
<header class="agentic-spec-card__header">
<h3><?php echo esc_html($submission->post_title); ?></h3>
<span class="badge"><?php echo esc_html("Clúster #{$clusterId}"); ?></span>
</header>
<div class="agentic-spec-card__body">
<?php
// Salida segura de HTML formateado con wp_kses_post
echo wp_kses_post($submission->post_content);
?>
</div>
<footer class="agentic-spec-card__footer">
<a href="<?php echo esc_url("mailto:{$email}"); ?>" class="email-link">
<?php echo esc_html($email); ?>
</a>
<a href="<?php echo esc_url($statusUrl); ?>" class="button button-secondary">
<?php echo esc_html__('Abrir en consola', 'agentic-lab'); ?>
</a>
</footer>
</article>
<?php
}
}
(new SecureSubmissionGateway())->registerHooks();
Valide tokens mediante check_admin_referer() para formularios POST, check_ajax_referer() para AJAX o la cabecera X-WP-Nonce para endpoints REST.
Compruebe con current_user_can() que el usuario actual posee una capacidad explícita, no únicamente que esté autenticado.
Procese las entradas con sanitize_text_field(), sanitize_email(), absint() y persista datos canónicos limpios en la base de datos.
En la salida, aplique esc_html() para texto, esc_attr() para atributos, esc_url() para enlaces y wp_kses() para marcado estructurado.