WordPress 7.1.4 came out on 28 October 2026 with fixes for what WordPress counts as 22 vulnerabilities across 17 advisories. 7.1.3, three weeks ago, had seven.
Almost everything here needs an account first, usually a Contributor. The exception is a race condition in the upgrade process that ends in remote code execution with no account at all. Backports shipped the same day for every supported branch down to 4.7.39.
| Vulnerability Patched | Requirements | Reported By |
| Race condition in the core upgrade process, leading to RCE | Nothing (Unauthenticated), but the attacker has to hit the seconds-long window while an update runs, and wp-content/upgrade/ must be reachable over the web | Umut Bayram |
| Stored XSS via whitespace in block delimiters | A Contributor+ account, and an editor or admin opening the post in the block editor | Rafie Muhammad, Awesome Motive |
| Stored XSS on the personal data erasure requests screen | An Editor+ account, and an admin opening Tools → Erase Personal Data | Anthropic |
| Unpublished Customizer changesets exposed | Nothing (Unauthenticated) | Antonio Lorensius Silaban |
| Unescaped usernames on the multisite Network Users screens | A username containing markup, stored by something other than core, plus a network admin viewing the list (hardening fix) | Anthropic |
| Notes readable, editable and deletable without post access | A custom role or post type that grants moderate_comments without edit access to the post | Ramon, WordPress Security Team |
| Stored XSS in Customizer selective refresh | A Contributor+ account, and an admin opening the Customizer on that content | Rafie Muhammad, Awesome Motive |
Stored XSS via javascript: URLs in Interactivity API bindings | A Contributor+ account | Luis Herranz, and multiple independent reporters |
| Stored XSS via Interactivity API event handler bindings | A Contributor+ account | Jon Surrell, WordPress Security Team |
| Stored XSS via prototype pollution in Interactivity API context | A Contributor+ account | rekter0 |
| Stored XSS and denial of service in Interactivity API bindings | A Contributor+ account | rekter0, Marcel Karlsson, Jon Surrell, Alex Concha |
object elements loading admin pages via Interactivity API bindings | A Contributor+ account | rekter0 |
| Stored XSS in the Interactivity Router via uploaded text files | An Author+ account (upload capability) | Anas cherni |
| Post content shadowing Script Module data via a matching element ID | A Contributor+ account | Weston Ruter |
| Stored XSS in the Categories block via format string injection | A Contributor+ account | Ben Bidner, WordPress Security Team |
Unsafe preg_replace() replacements in the Image and Playlist blocks | An Author+ account (hardening fix) | Ben Bidner, WordPress Security Team |
| Stored XSS in Comments Pagination and Post Navigation Link block labels | A Contributor+ account | Alex Concha, WordPress Security Team |
What the Code Shows
The Upgrade Process RCE
When WordPress installs or updates anything, it extracts the package into wp-content/upgrade/, which the web server serves like any other directory. Until 7.1.4 the extraction folder took its name from the package, so for anything uploaded through the admin the name was fully predictable. The cleanup routine that runs first deleted everything in that directory, including the index.php that stops the folder being listed.
For the few seconds an update takes, a complete WordPress install sits at a guessable and browsable path. The extracted copy includes wp-admin/setup-config.php, which boots on its own without the site’s configuration. Reaching it in time means running the installer against a database you control, inside someone else’s document root.
7.1.4 appends 16 random characters to the folder name, recreates index.php and stops deleting it. A second change in update-core.php, not mentioned in the release post, adds wp-config.php to the list of files never copied out of the working directory.
The window is tiny. You have to request the right path at the moment an update runs, which is why we scored it AC:H and would not call it practical to exploit. It still needs no account and no interaction, the sink dates back to WordPress 2.8, and the window returns on every update, including unattended ones.
A PHP firewall cannot block it. The extracted copy loads WordPress from its own files, and the official zip ships no mu-plugins directory, so the site’s firewall never runs for that request. Block it at the web server: deny access to wp-content/upgrade/, and check the directory is not listable. Between updates it holds nothing but an index.php.
Interactivity API
Seven of the 17 advisories are Interactivity API bugs with a common root. KSES, which decides what HTML a user without unfiltered_html can save, allows any data-* attribute with any value, and every Interactivity directive is a data-* attribute: data-wp-interactive, data-wp-context, data-wp-bind--href. A Contributor could write directives into a post, have them pass filtering untouched, and the client runtime would act on them when a visitor loaded the page.
From there: bindings that set javascript: URLs, bindings onto on* handlers the PHP side already refused, reserved keys like __proto__ reaching the context merge, bindings on object elements that get past the data and type checks KSES performs, and a CSS parser that reprocessed its input on every iteration. That last one is the denial of service, and it hangs the visitor’s browser tab, not the server. 7.1.4 fixes these in three places: KSES strips bindings that would change a value it validated, the PHP directive processor restricts bindings on object elements, and the runtime refuses the unsafe targets.
The Interactivity client runtime shipped in 6.4, one release before the PHP class in 6.5, which is why WordPress lists 6.4 and 6.5 as affected by the same number of issues.
Script Module Data Shadowing
The Interactivity API reads its state from a script element found with getElementById(), which returns the first element with that ID whatever its tag. WordPress prints the real data block on wp_footer, after post content, so stored content carrying the same ID gets there first and supplies the runtime’s state. 7.1.4 checks that the element is actually a script element.
Customizer
Selective refresh built partial definitions from data-customize-partial-* attributes in the preview document, so post content could declare a partial whose selector went to jQuery’s $(). jQuery treats a string starting with < as HTML to build rather than a selector to match, the same behaviour behind one of the 7.1.3 fixes. 7.1.4 uses .find(), which only reads selectors.
Separately, a Customizer preview request with no changeset UUID adopted the most recent unpublished changeset, whoever wrote it, with no capability check, giving an anonymous visitor the UUID and the rendered preview. Setting values stayed protected by their own capabilities. 7.1.4 requires the customize capability.
Blocks
In three block fixes, content that had already been filtered was reinterpreted afterwards. The Categories block ran its dropdown label through wp_kses_post() and then put the result into a sprintf() format string, so format directives in the label were expanded after filtering finished. The Image and Playlist blocks built markup with preg_replace() using attacker-influenced matched text as the replacement string, so PHP expanded backreferences like $0 inside it; without a replacement limit that could splice one tag into another tag’s attribute. Both now use the HTML API. The Comments Pagination and Post Navigation Link blocks printed their label attribute without escaping it.
Block Delimiter Whitespace
WordPress parses block delimiters twice, in PHP on the server and in JavaScript in the editor, and the two disagreed on what counts as whitespace. PHP’s \s matches six characters; JavaScript’s also matches non-breaking spaces, various Unicode spaces and a byte order mark. A delimiter using one of those is an inert HTML comment to PHP and a real block, with attacker-controlled attributes, to the editor. 7.1.4 pins every parser to the same five characters.
Notes Permissions
The REST comments controller took moderate_comments as enough to edit a note, so a role holding it could read, change and delete notes on posts it could not edit. On a default install this is moot, because administrators and editors hold moderate_comments and edit_others_posts together. It matters where something separates the two: a custom role, a plugin granting the capability, or a post type with its own capability set. 7.1.4 requires edit access to the post the note belongs to.
Timeline
Update Now
WordPress 7.1.4 is in the Dashboard under Updates or on WordPress.org, and sites with background updates will take it on their own. Backports are already available: 7.0.8, 6.9.11, 6.8.12, 6.7.11, 6.6.11, 6.5.14, 6.4.14 and down to 4.7.39. Sites on 4.6 or older get nothing.
Two things beyond updating. Check that wp-content/upgrade/ is not browsable, which closes the only issue here that needs no account. Then check who holds Contributor and above, since that is where all but two of these start.



