Modernizing wp_kses(): Inside WordPress Core’s HTML API Refactor

Silhouette photography of man – Modernizing wp_kses(): Inside WordPress Core’s HTML API Refactor

The Evolution of Content Sanitization in WordPress

Since its earliest iterations, WordPress has relied on the wp_kses() subsystem to sanitize HTML markup. Originally adapted from the standalone KSES library (“XSS access”), its role was twofold: repair aesthetic markup defects resulting from manual typing or bad copy-pasting, and eliminate cross-site scripting (XSS) risks from untrusted input. However, the original library was architected prior to the HTML5 specification during an era dominated by hand-crafted HTML and strict XHTML conventions.

Modern web publishing relies heavily on structured block editors and spec-compliant browser rendering engines. There is no longer a practical concept of fundamentally untreatable HTML, as modern parsing standards dictate exact rendering behaviors for all edge cases. As part of Trac ticket #66208 and Pull Request #13271, WordPress Core is overhauling wp_kses() by rebuilding its internal processing pipeline on top of the native HTML API.

Under the Hood: Replacing PCRE Chains with the HTML API

The legacy wp_kses() infrastructure operated through an intricate web of procedural functions, custom string manipulations, and heavily nested PCRE regular expressions. This approach presented inherent maintenance challenges and subtle parsing inaccuracies when encountering complex HTML structures.

The refactored implementation replaces string-matching patterns with structural parsing via the HTML API. By leveraging the low-level stream parsing mechanisms of the Tag Processor and HTML Processor, wp_kses() can tokenize and evaluate HTML markup strictly according to HTML5 specification rules. This architectural shift significantly improves processing performance, reduces memory overhead, and guarantees that WordPress interprets HTML input in exact alignment with modern browser behavior.

HTML Normalization and Fresh Document Serialization

A fundamental structural shift in the updated wp_kses() is how output is generated. The legacy implementation modified the original input string in-place by removing or replacing targeted tokens. The new implementation parses the document structurally and constructs a brand-new serialization string as it iterates through the input stream.

This fresh serialization enforces strict attribute and node normalization across all processed markup:

  • Tag names are standardized to lowercase.
  • Attribute values are uniformly wrapped in double quotes.
  • Non-normative self-closing slashes on void elements (such as <img /> or <br />) are omitted.
  • Character references within text nodes are fully decoded and deterministically re-escaped.

Consider the following transformation of raw input:

<!-- Input HTML -->
The <IMG class=bike class="bo&#x0061;t" src='vehicle.png' /> & the wheels&hellip;

<!-- Legacy output -->
The <IMG class="bike" src="vehicle.png" /> &amp; the wheels&hellip;

<!-- New HTML API output -->
The <img class="bike" src="vehicle.png"> &amp; the wheels…

In addition, defensive dropping of text that resembles tags is replaced with explicit escaping. For instance, an unescaped condition like <$500 was previously stripped by legacy regular expressions that mistook it for an unclosed tag. The new parser recognizes the text context and safely converts it to &lt;$500.

Special Atomic Elements: Complete Content Stripping for Script and Style

The HTML specification categorizes specific elements—such as <script>, <style>, <title>, and <textarea>—as special atomic elements (or raw text / escapable raw text elements). The content inside these elements is parsed as plaintext using specialized state rules where child HTML tags cannot exist.

In the legacy wp_kses() implementation, if a <script> tag was not explicitly permitted in the allowed tags array (such as in standard post context), the parser stripped the opening and closing HTML tags but left the raw inner text intact on the page. While this neutralized code execution, it routinely left visible JavaScript string fragments rendered on screen.

Under the new HTML API architecture, special atomic elements are treated as indivisible units. If an atomic element tag is disallowed, the parser strips both the tags and the entire inner content block:

<!-- Input HTML -->
Click on my <script>alert(1)</script>!

<!-- Legacy output -->
Click on my alert(1)!

<!-- New HTML API output -->
Click on my !

Handling Truncated Markup and Ambiguous Tail-End Inputs

Input submitted to wp_kses() often consists of incomplete HTML fragments rather than complete standalone documents. However, this output is ultimately rendered inside a surrounding DOM hierarchy. Safeguarding the end of an input fragment is necessary to prevent malformed tail-end tokens from breaking downstream page layout.

When encountering incomplete syntax at the terminus of a string (such as an unclosed tag prefix), legacy wp_kses() converted the trailing fragment into escaped plain text. The new implementation pauses execution upon encountering truncated syntax indicators and discards the ambiguous trailing sequence entirely:

<!-- Input HTML -->
Click on the <butto

<!-- Legacy output -->
Click on the &lt;butto

<!-- New HTML API output -->
Click on the 

This truncation logic applies strictly to incomplete special atomic elements as well. If an opening <title> or <style> tag lacks a matching closing tag within the input string, the entire incomplete node sequence is safely removed.

SVG, MathML, and Boundary Rejection Logic

Inline SVG and MathML present complex edge cases in web sanitization. While natively embedded in HTML documents, SVG and MathML are XML applications that strictly enforce XML parsing and error-handling constraints. When modern HTML parsers encounter embedded SVG or MathML, they switch between HTML-like and XML-like state transitions.

Because full tree construction and multi-namespace state tracking require significant overhead, the refactored wp_kses() utilizes standard stream checks via the Tag Processor. The function processes SVG and MathML sequences up to the point where fully deterministic parsing can be guaranteed. If a syntax structure requires complex XML context switching that introduces ambiguity, wp_kses() aborts further deep parsing of that subtree and returns the valid sanitized markup preceding the boundary.

Preserving Unbalanced Tags and Resolving Legacy Anomalies

Despite strict enforcement of HTML rules, certain historical behaviors have been preserved to avoid breaking production plugins and themes. Most notably, wp_kses() continues to allow unbalanced markup (such as an unclosed <div> tag). Because developers frequently run sanitization routines on isolated HTML chunks prior to string concatenation, automatically auto-closing open tags would break common dynamic layout patterns.

Conversely, non-standard and broken legacy behaviors are being systematically corrected. For example, legacy wp_kses() produced inconsistent stripping behavior when handling HTML tags nested inside non-raw text elements like <title>. Under the HTML API, non-executable container elements properly escape all inner markup text references rather than selectively dropping mismatched elements.

Managing Compatibility: The Legacy Parser Fallback Filter

The refactored wp_kses() implementation introduces no function signature alterations or developer API deprecations. It functions as a direct, drop-in performance and security upgrade. However, if legacy custom code relies heavily on the specific string manipulation quirks of the old regex engine, WordPress Core provides an explicit opt-out filter hook.

Developers can force WordPress to revert to the legacy regular expression parsing engine by declaring the following filter during site bootstrap or plugin initialization:

add_filter( 'wp_kses_force_legacy_parser', '__return_true' );

This fallback filter is designed as a temporary migration aid. Core developers intend to phase out and completely remove the legacy PCRE code path in subsequent WordPress releases once broad field testing confirms stability.

Frequently asked questions

Does the new wp_kses() implementation change function signatures or parameter formats?

No. The refactor is an in-place upgrade. All function signatures, parameter orders, and return types remain identical to legacy versions.

How does the updated wp_kses() handle unallowed or tags?

Unlike the legacy implementation which removed the tags while leaving raw script content on the page, the HTML API refactor removes both the tags and their entire inner contents if the tag is disallowed.

How can developers force WordPress to use the legacy wp_kses() engine?

You can enable the legacy parser by adding the filter 'add_filter( 'wp_kses_force_legacy_parser', '__return_true' );' in a custom plugin or theme functions.php file.

Will unbalanced HTML tags be automatically closed by the new parser?

No. Unbalanced tags are intentionally preserved to maintain backward compatibility with code that sanitizes fragmented HTML chunks prior to concatenation.

Primary reference: Review the original announcement for exact release details. This article is an independent explanation and does not reproduce the source text.