wp_kses() está detrás de wp_kses_post(), de la limpieza de comentarios, del texto de widgets y de cada llamada wp_kses( $html, $allowed ) de tu plugin. En trunk ahora funciona sobre la HTML API de WordPress en lugar de expresiones regulares. Según el informe de progreso de Dennis Snell, el cambio debería llegar a WordPress 7.2 o 7.3, según lo que descubran las pruebas. Este artículo explica qué cambia en la salida, cómo localizar el código tuyo que se ve afectado y para qué sirve la salida de emergencia temporal.
¿Qué cambia exactamente en wp_kses()?
La implementación antigua desmontaba el marcado con expresiones regulares. La nueva lo lee con el tokenizador de la HTML API, decide por etiqueta y atributo qué se permite y vuelve a escribir el resultado. El pull request se titula “KSES: Reimplement with Tag Processor” (PR 13271) y está vinculado al ticket de Trac 66208. Según el PR, el código nuevo vive en una función llamada wp_sanitize_html_kses().
La intención del informe es clara: wp_kses() no debe quedar peor de lo que estaba. El informe dice además que plugins y temas no deberían necesitar cambios. Ambas frases pueden ser ciertas y tus pruebas ponerse rojas igualmente, porque “igual de seguro” no significa “los mismos bytes”. La nueva serialización es el centro del cambio, y cambia cadenas.
Fuente: Progress Report: wp_kses(), 7 de octubre de 2026.
¿Qué entradas producen una salida distinta?
El informe muestra pares de antes y después. El bloque de abajo se basa en ellos (mi array de etiquetas permitidas, las entradas del informe). Ejecuta los casos en tu propio build de trunk antes de fiarte de mi transcripción.
<?php
// Modelled on the examples in the Make Core progress report.
$allowed = array(
'a' => array( 'href' => true ),
'code' => true,
'img' => array( 'src' => true, 'class' => true ),
);
wp_kses( 'Click on my <script>alert(1)</script>!', $allowed );
// legacy: Click on my alert(1)!
// new: Click on my !
wp_kses( 'The <IMG class=bike class="boat" src=\'vehicle.png\' /> & the wheels…', $allowed );
// legacy: The <IMG class="bike" src="vehicle.png" /> & the wheels…
// new: The <img class="bike" src="vehicle.png"> & the wheels…
wp_kses( 'Add <code>/?preview=true§ion=grilling</code>.', $allowed );
// legacy: Add <code>/?preview=true&section=grilling</code>.
// new: Add <code>/?preview=true§ion=grilling</code>.
wp_kses( 'Click on the <butto', $allowed );
// legacy: Click on the <butto
// new: Click on the (trailing space, nothing else)El informe agrupa las diferencias así:
- Normalización. Los nombres de etiqueta pasan a minúsculas, los atributos llevan comillas dobles, si un atributo se repite gana el primero y las referencias de caracteres se reescriben.
- Corchetes angulares sueltos. Texto como
<$500o>5yose tragaba. Ahora se convierte en<y>, y el texto sobrevive. - Ampersand ambiguo. Una referencia con nombre sin punto y coma, como
§ionen una URL dentro de un nodo de texto, se decodifica como lo hace el navegador. Por eso el tercer caso produce el signo de párrafo. - Script y style. Cuando estos elementos no están permitidos, su contenido desaparece junto con las etiquetas. El código antiguo dejaba el contenido como texto visible.
- Entrada truncada. Una última etiqueta incompleta se descarta, no se escapa.
- Elementos especiales. El texto con forma de etiqueta dentro de un
<title>permitido se escapa, mientras que el mismo texto dentro de un<div>desaparece con la etiqueta desconocida. - SVG y MathML. El contenido que exige un análisis complejo hace que la función se detenga y devuelva solo lo anterior a ese punto. El informe muestra un
<li>con MathML y un<span>anidado cortado ahí. - Límites duros. Algunos comentarios mal formados,
NOSCRIPTy elementos confundibles se rechazan ahora. - Etiquetas desbalanceadas. Se conservan a propósito, porque el código existente depende de ello.
En una tienda WooCommerce típica, los primeros sitios donde mirar son descripciones de producto con medidas como “<5 cm” pegadas desde la hoja de cálculo del proveedor, HTML importado de proveedores con etiquetas en mayúsculas y constructores de páginas que imprimen SVG en línea. Es una suposición sobre dónde buscar, no una medición.
En las fuentes que leí no encontré confirmado cómo se comporta <script> cuando lo permites de forma expresa en el array. Prueba ese caso tú mismo en lugar de dar por hecho un resultado.
¿Mi array de allowed html sigue funcionando?
Casi siempre, porque la lógica de permisos es la misma: una etiqueta o un atributo está en el array o no está. Lo que cambia es lo que sale alrededor. Tres patrones merecen una mirada.
- Arrays que permiten
scriptostyleen línea. Un shortcode que imprime un script en línea y lo pasa porwp_kses()conscriptpermitido está justo en la zona que toca el informe. Compara la salida antigua con la nueva. Plantéate también si el script no debería ir porwp_add_inline_script()en lugar de pasar por el sanitizador. - Arrays que permiten
svgomath. El caso habitual son los conjuntos de iconos en línea. Si se aplica la regla del informe, la salida puede cortarse en la primera construcción que el parser no sepa tratar. Compara cada icono que entregues. - Arrays para
<title>y similares.<title>es un elemento especial y su contenido se trata como texto. De ahí el ejemplo con<sneaky>escapado.
Si tu array solo permite a, strong, em, img y unos pocos atributos, el efecto probable es cosmético: <IMG ... /> pasa a <img ...>.
¿Qué se rompe en las pruebas que comparan HTML saneado?
Todo lo que comprueba una cadena exacta. Una prueba como la primera aserción de abajo pasa hoy y puede fallar tras la reescritura, aunque el marcado signifique lo mismo. La segunda aserción sobrevive a la normalización.
<?php
// Brittle: pins the exact string the sanitizer happens to emit.
$this->assertSame( '<a href="/x">Read</a>', wp_kses_post( "<A HREF='/x'>Read</A>" ) );
// Sturdier: asserts on what the markup means.
$p = new WP_HTML_Tag_Processor( wp_kses_post( "<A HREF='/x'>Read</A>" ) );
$this->assertTrue( $p->next_tag( 'a' ) );
$this->assertSame( '/x', $p->get_attribute( 'href' ) );Fija el comportamiento, no los bytes. En los fixtures que deben seguir siendo literales, vuelve a generarlos una vez en trunk, lee el diff como persona y haz commit del nuevo valor esperado con un comentario que diga desde qué versión cambió. Un “actualizar snapshots” en bloque es la forma en que una regresión real acaba aprobada.
Las pruebas visuales y los archivos de referencia piden el mismo cuidado. Las plantillas de correo que sanes antes de enviar son un lugar típico donde se compara una cadena guardada con una recién generada.
¿Cómo pruebo en trunk sin tocar producción?
Tres caminos, todos en staging o en local. Son sugerencias mías, no parte del informe oficial.
Plugin WordPress Beta Tester. Puede mover un sitio de staging a las compilaciones nocturnas y luego a las betas de 7.2. Según el calendario, la Beta 1 sale el 20 de octubre de 2026. No pude confirmar si la reescritura ya está en la Beta 1, así que comprueba que el filtro wp_kses_force_legacy_parser existe en el código que instalaste.
WP-CLI nightly. En una copia desechable:
# Staging only, never production.
wp core update --version=nightly --force
wp core version
wp eval-file bin/kses-diff.php > kses-diff.txt
tail -n 1 kses-diff.txtwp-env. Apunta el core al espejo de trunk y monta tu plugin:
{
"core": "WordPress/WordPress#master",
"plugins": [ "." ]
}Elijas el camino que elijas, termina confirmando que el filtro existe, por ejemplo con grep -rn wp_kses_force_legacy_parser wp-includes/. Si no está, no estás probando la reescritura.
¿Cómo comparo la salida saneada de contenido real?
Los casos sintéticos solo encuentran errores que ya imaginaste. La prueba útil es tu propia base de datos. El script de abajo sanea cada entrada publicada dos veces, una con el parser antiguo forzado y otra sin forzarlo, e imprime solo las entradas que difieren.
<?php
// bin/kses-diff.php
// Run on a trunk build: wp eval-file bin/kses-diff.php > kses-diff.txt
$ids = get_posts( array(
'post_type' => 'any',
'post_status' => 'publish',
'numberposts' => -1,
'fields' => 'ids',
) );
$diff = 0;
foreach ( $ids as $id ) {
$raw = get_post_field( 'post_content', $id );
add_filter( 'wp_kses_force_legacy_parser', '__return_true' );
$old = wp_kses_post( $raw );
remove_filter( 'wp_kses_force_legacy_parser', '__return_true' );
$new = wp_kses_post( $raw );
if ( $old !== $new ) {
++$diff;
printf( "== %d %s\n--- old\n%s\n+++ new\n%s\n", $id, get_permalink( $id ), $old, $new );
}
}
printf( "%d of %d posts differ\n", $diff, count( $ids ) );Ejecútalo sobre una copia de la base de datos de producción, porque los editores pegan cosas raras. Lee a mano los primeros veinte diffs. Cuenta con un puñado de causas repetidas (etiquetas autocerradas, un < suelto en una medida, SVG en línea). Corregir una causa corrige muchas entradas de golpe.
Amplía la red con el mismo patrón: sustituye wp_kses_post() por la llamada que hace tu plugin con su propio array y aliméntala con valores de opciones, texto de widgets y comentarios, no solo con entradas.
¿Cuándo uso la salida de emergencia del parser antiguo?
El filtro se llama wp_kses_force_legacy_parser. Si devuelve true, se restablece el parser antiguo. El informe lo describe como una forma de que los dueños de sitios no ejecuten el código de reemplazo, y la palabra que importa es temporal.
<?php
// wp-content/mu-plugins/kses-legacy-parser.php
// Stopgap only. Delete it once your output diff is clean.
add_filter( 'wp_kses_force_legacy_parser', '__return_true' );Hay dos situaciones. La primera: encontraste una regresión en el sitio de un cliente durante la ventana de pruebas y necesitas mantenerlo estable mientras corriges el código o reportas el fallo. La segunda: un plugin que no controlas deja de funcionar y su autor no ha publicado arreglo. Pon el filtro en un mu-plugin para que sea visible en un solo sitio y asócialo a una tarea con fecha de retirada.
No lo entregues como valor permanente. La implementación antigua tiene problemas conocidos que la reescritura quiere resolver, entre ellos caídas de PCRE con valores de atributo grandes y contenido de scripts mostrado como texto visible, según la descripción del PR. Una salida silenciosa conserva esos problemas y oculta la migración al siguiente desarrollador.
No encontré una fecha confirmada para retirar el parser antiguo. Trata el filtro como algo que va a desaparecer y planifica en consecuencia.
¿Cuál es el calendario de WordPress 7.2?
Del artículo del calendario de 7.2, siempre a las 15:00 UTC:
| Hito | Fecha |
|---|---|
| Beta 1 | 20 de octubre de 2026 |
| Beta 2 | 27 de octubre de 2026 |
| Beta 3 | 3 de noviembre de 2026 |
| Beta 4 | 10 de noviembre de 2026 |
| RC1 | 17 de noviembre de 2026 |
| RC2 | 24 de noviembre de 2026 |
| RC3 | 1 de diciembre de 2026 |
| Versión final | 8 de diciembre de 2026 |
El informe dice 7.2 o 7.3. No puedes contar con ninguna de las dos, pero tampoco descartar la 7.2. Un plan sensato: pasar el script de diff en staging antes de la Beta 1, de nuevo tras la RC1, y reportar en el ticket de Trac todo lo que sorprenda mientras la ventana siga abierta. Es justo la opinión que pide el autor.
¿Qué debería hacer una agencia esta semana?
- Buscar en el código
wp_kses(,wp_kses_post(,wp_kses_data(y arrays$allowedpropios. Anotar cuáles reciben HTML de usuarios o editores. - Montar un staging en trunk por uno de los caminos anteriores y confirmar que el filtro existe.
- Ejecutar el script de diff contra una copia del contenido de producción. Clasificar por causa, no por entrada.
- Reescribir las pruebas frágiles para que comprueben estructura y regenerar una vez los fixtures de texto, con revisión humana.
- Revisar shortcodes y bloques que emiten script, style, SVG o MathML en línea.
- Decidir por cliente si hace falta la salida de emergencia, documentarla y fijar una fecha de retirada.
Si el proyecto arrastra años de saneado propio alrededor de un ecosistema de plugins, esta auditoría encaja en nuestro servicio de desarrollo WordPress a medida.
Fuentes
- Progress Report: wp_kses(), Make Core, 7 de octubre de 2026. Ejemplos, nombre del filtro, la frase sobre 7.2 o 7.3.
- Calendario de la release party de 7.2, Make Core, 6 de octubre de 2026.
- Pull request 13271, KSES: Reimplement with Tag Processor. Nombre de la función, filtro, lista de cambios de comportamiento. Los tickets de Trac 66208 y 65984 se mencionan allí.
- Ticket de Trac 66208. No pude abrir el ticket directamente, así que los datos sobre él proceden de la página del PR.
Verificado el 10 de octubre de 2026. El comportamiento de trunk puede cambiar antes del lanzamiento.






