wp_kses() steckt hinter wp_kses_post(), der Kommentarbereinigung, Widget-Texten und jedem wp_kses( $html, $allowed ) in Ihrem Plugin. Im Trunk läuft es jetzt auf der WordPress HTML API statt auf regulären Ausdrücken. Laut dem Fortschrittsbericht von Dennis Snell soll die Änderung in WordPress 7.2 oder 7.3 landen, je nachdem, was die Tests ergeben. Dieser Beitrag zeigt, was sich im Output ändert, wie Sie Ihren betroffenen Code finden und wofür der befristete Opt-out gedacht ist.
Was ändert sich an wp_kses() genau?
Die alte Implementierung zerlegte Markup mit regulären Ausdrücken. Die neue liest es mit dem Tokenizer der HTML API, entscheidet je Tag und Attribut, was erlaubt ist, und schreibt das Ergebnis neu. Der Pull Request heißt “KSES: Reimplement with Tag Processor” (PR 13271) und hängt am Trac-Ticket 66208. Laut PR liegt der neue Code in einer Funktion namens wp_sanitize_html_kses().
Die Absicht im Bericht ist eindeutig: wp_kses() soll nicht schlechter werden als vorher. Außerdem heißt es dort, Plugins und Themes sollten keine Änderungen brauchen. Beides kann stimmen, und Ihre Tests können trotzdem rot werden, denn “gleich sicher” heißt nicht “gleiche Bytes”. Die erneute Serialisierung ist der Kern der Änderung, und sie ändert Strings.
Quelle: Progress Report: wp_kses(), 7. Oktober 2026.
Welche Eingaben liefern ein anderes Ergebnis?
Der Bericht nennt Vorher-Nachher-Paare. Der Block unten ist daran angelehnt (mein Array erlaubter Tags, die Eingaben aus dem Bericht). Lassen Sie die Fälle auf Ihrem eigenen Trunk-Build laufen, bevor Sie meiner Abschrift trauen.
<?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)Der Bericht gruppiert die Unterschiede so:
- Normalisierung. Tag-Namen werden kleingeschrieben, Attribute bekommen doppelte Anführungszeichen, bei doppelten Attributen gewinnt das erste, Zeichenreferenzen werden neu geschrieben.
- Lose spitze Klammern. Text wie
<$500oder>5yowurde früher verschluckt. Jetzt wird er zu<und>, der Text bleibt erhalten. - Mehrdeutiges kaufmännisches Und. Eine benannte Referenz ohne Semikolon, etwa
§ionin einer URL im Textknoten, wird so dekodiert wie im Browser. Deshalb ergibt der dritte Fall das Paragrafenzeichen. - Script und style. Sind diese Elemente nicht erlaubt, verschwindet ihr Inhalt samt Tags. Der alte Code ließ den Inhalt als sichtbaren Text stehen.
- Abgeschnittene Eingabe. Ein unvollständiges letztes Tag wird verworfen, nicht escaped.
- Spezielle Elemente. Tag-ähnlicher Text in einem erlaubten
<title>wird escaped, derselbe Text in einem<div>verschwindet mit dem unbekannten Tag. - SVG und MathML. Inhalt, der aufwendiges Parsen braucht, lässt die Funktion abbrechen und nur das zurückgeben, was davor stand. Der Bericht zeigt ein
<li>mit MathML und verschachteltem<span>, das an dieser Stelle gekappt wird. - Harte Grenzen. Manche defekten Kommentare,
NOSCRIPTund verwechselbare Elemente werden jetzt abgelehnt. - Unausgeglichene Tags. Bleiben bewusst erhalten, weil bestehender Code darauf baut.
Typische Kandidaten in einem deutschsprachigen Shop: Produktbeschreibungen mit Maßangaben wie “<5 cm” aus einer Lieferantentabelle, Texte aus Rechtstext-Generatoren mit eingebettetem HTML und Cookie-Hinweise, die ein Inline-Skript ausgeben. Das ist eine Vermutung, wo Sie zuerst suchen sollten, keine Messung.
Ob <script> sich anders verhält, wenn Sie es im Array ausdrücklich erlauben, habe ich in den gelesenen Quellen nicht bestätigt gefunden. Prüfen Sie diesen Fall selbst, statt ein Ergebnis anzunehmen.
Funktioniert mein eigenes Allowed-HTML-Array weiter?
Meistens ja, denn die Erlaubnislogik bleibt gleich: Ein Tag oder Attribut steht im Array oder nicht. Geändert hat sich, was drumherum herauskommt. Drei Muster verdienen einen Blick.
- Arrays, die Inline-
scriptoderstyleerlauben. Ein Shortcode, der ein Inline-Skript ausgibt und durchwp_kses()mit erlaubtemscriptschickt, liegt genau in dem Bereich, den der Bericht berührt. Vergleichen Sie alte und neue Ausgabe. Prüfen Sie auch, ob das Skript besser überwp_add_inline_script()laufen sollte als durch den Sanitizer. - Arrays, die
svgodermatherlauben. Typisch sind Inline-Icon-Sets. Gilt die Regel aus dem Bericht, kann die Ausgabe am ersten Konstrukt abbrechen, das der Parser nicht verarbeitet. Vergleichen Sie jedes ausgelieferte Icon. - Arrays für
<title>und Ähnliches.<title>ist ein spezielles Element, sein Inhalt gilt als Text. Daher das Beispiel mit dem escapten<sneaky>.
Erlaubt Ihr Array nur a, strong, em, img und ein paar Attribute, bleibt der Effekt vermutlich kosmetisch: Aus <IMG ... /> wird <img ...>.
Was geht in Tests kaputt, die bereinigtes HTML vergleichen?
Alles, was einen exakten String prüft. Ein Test wie die erste Assertion unten besteht heute und kann nach der Umstellung scheitern, obwohl das Markup dasselbe bedeutet. Die zweite Assertion übersteht die Normalisierung.
<?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' ) );Prüfen Sie Verhalten, nicht Bytes. Fixtures, die stringgenau bleiben müssen, erzeugen Sie einmal auf Trunk neu, lesen den Diff als Mensch und committen den neuen Sollwert mit einem Kommentar, ab welcher Version er gilt. Ein pauschales “Snapshots aktualisieren” ist der Weg, auf dem eine echte Regression durchgewunken wird.
Visuelle Tests und Golden-Files brauchen dieselbe Sorgfalt. E-Mail-Templates, die Sie vor dem Versand bereinigen, sind ein typischer Ort, an dem ein gespeicherter String mit einem frischen verglichen wird.
Wie teste ich auf Trunk, ohne Produktion anzufassen?
Drei Wege, alle auf Staging oder lokal. Das sind meine Vorschläge, kein Teil des offiziellen Berichts.
Plugin WordPress Beta Tester. Es stellt eine Staging-Seite auf Nightlies und später auf die 7.2-Betas um. Laut Zeitplan erscheint Beta 1 am 20. Oktober 2026. Ob die Umstellung schon in Beta 1 steckt, konnte ich nicht bestätigen. Prüfen Sie daher, ob der Filter wp_kses_force_legacy_parser im installierten Code existiert.
WP-CLI Nightly. Auf einer Wegwerfkopie:
# 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. Den Core auf den Trunk-Spiegel zeigen lassen und das Plugin einhängen:
{
"core": "WordPress/WordPress#master",
"plugins": [ "." ]
}Egal welcher Weg: Bestätigen Sie am Ende, dass der Filter existiert, etwa mit grep -rn wp_kses_force_legacy_parser wp-includes/. Fehlt er, testen Sie die Umstellung nicht.
Wie vergleiche ich die bereinigte Ausgabe echter Inhalte?
Synthetische Fälle finden nur Fehler, die Sie sich schon ausgedacht haben. Der nützliche Test ist Ihre eigene Datenbank. Das Skript unten bereinigt jeden veröffentlichten Beitrag zweimal, einmal mit erzwungenem Legacy-Parser, einmal ohne, und gibt nur die abweichenden Beiträge aus.
<?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 ) );Lassen Sie es auf einer Kopie der Produktionsdatenbank laufen, denn Redakteure fügen merkwürdige Dinge ein. Lesen Sie die ersten zwanzig Diffs von Hand. Rechnen Sie mit wenigen wiederkehrenden Ursachen (selbstschließende Tags, ein loses < in einer Maßangabe, Inline-SVG). Wer eine Ursache behebt, behebt viele Beiträge auf einmal.
Erweitern Sie das Netz nach demselben Muster: Ersetzen Sie wp_kses_post() durch den Aufruf, den Ihr Plugin mit eigenem Array macht, und füttern Sie ihn mit Optionswerten, Widget-Text und Kommentaren, nicht nur mit Beiträgen.
Wann nutze ich den Opt-out für den Legacy-Parser?
Der Filter heißt wp_kses_force_legacy_parser. Gibt er true zurück, ist der Legacy-Parser wieder aktiv. Der Bericht beschreibt ihn als Möglichkeit für Seitenbetreiber, den Ersatzcode nicht auszuführen. Entscheidend ist das Wort befristet.
<?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' );Zwei Situationen rechtfertigen ihn. Erstens: Sie haben im Testfenster eine Regression auf einer Kundenseite gefunden und müssen die Seite stabil halten, bis Code oder Bugreport erledigt sind. Zweitens: Ein Plugin, das Sie nicht kontrollieren, bricht, und der Hersteller hat keinen Fix. Legen Sie den Filter in ein MU-Plugin, damit er an einer Stelle sichtbar ist, und hängen Sie ein Ticket mit Löschdatum daran.
Liefern Sie ihn nicht als dauerhaften Standard aus. Die alte Implementierung hat bekannte Probleme, die die Umstellung beheben soll, laut PR-Beschreibung unter anderem PCRE-Abstürze bei großen Attributwerten und Skriptinhalt, der als sichtbarer Text erscheint. Ein stiller Opt-out behält diese Probleme und versteckt die Migration vor dem nächsten Entwickler.
Ein bestätigtes Datum für das Entfernen des Legacy-Parsers habe ich nicht gefunden. Behandeln Sie den Opt-out als etwas, das verschwindet, und planen Sie entsprechend.
Wie sieht der Zeitplan für WordPress 7.2 aus?
Aus dem Zeitplan-Beitrag zu 7.2, jeweils 15:00 UTC:
| Meilenstein | Datum |
|---|---|
| Beta 1 | 20. Oktober 2026 |
| Beta 2 | 27. Oktober 2026 |
| Beta 3 | 3. November 2026 |
| Beta 4 | 10. November 2026 |
| RC1 | 17. November 2026 |
| RC2 | 24. November 2026 |
| RC3 | 1. Dezember 2026 |
| Finale Version | 8. Dezember 2026 |
Der Bericht sagt 7.2 oder 7.3. Auf keine der beiden Versionen dürfen Sie fest zählen, 7.2 ausschließen können Sie aber auch nicht. Ein vernünftiger Plan: Diff-Skript auf Staging vor Beta 1, nochmal nach RC1, und alles Überraschende im Trac-Ticket melden, solange das Fenster offen ist. Genau dieses Feedback erbittet der Autor.
Was sollte eine Agentur diese Woche tun?
- Den Code nach
wp_kses(,wp_kses_post(,wp_kses_data(und eigenen$allowed-Arrays durchsuchen. Auflisten, welche HTML von Nutzern oder Redakteuren bekommen. - Ein Trunk-Staging über einen der Wege oben aufsetzen und prüfen, dass der Filter existiert.
- Das Diff-Skript gegen eine Kopie der Produktionsinhalte laufen lassen. Nach Ursache sortieren, nicht nach Beitrag.
- Brüchige Tests auf Struktur umstellen, String-Fixtures einmal neu erzeugen und von einem Menschen prüfen lassen.
- Shortcodes und Blöcke prüfen, die Inline-Script, Style, SVG oder MathML ausgeben.
- Pro Kunde entscheiden, ob der Opt-out nötig ist, ihn dokumentieren und ein Löschdatum setzen.
Wenn ein Projekt Jahre eigener Bereinigungslogik rund um ein Plugin-Ökosystem trägt, passt so ein Audit in unsere Leistung individuelle WordPress-Entwicklung.
Quellen
- Progress Report: wp_kses(), Make Core, 7. Oktober 2026. Beispiele, Filtername, Aussage zu 7.2 oder 7.3.
- Zeitplan der 7.2 Release Party, Make Core, 6. Oktober 2026.
- Pull Request 13271, KSES: Reimplement with Tag Processor. Funktionsname, Filter, Liste der Verhaltensänderungen. Die Trac-Tickets 66208 und 65984 sind dort genannt.
- Trac-Ticket 66208. Das Ticket selbst ließ sich nicht öffnen, die Angaben dazu stammen von der PR-Seite.
Zuletzt geprüft am 10. Oktober 2026. Das Verhalten von Trunk kann sich vor dem Release ändern.






