Published: June 12, 2025
On May 20, 2025, the HTML specification was updated to escape < and > in attributes, helping prevent mutation XSS (mXSS) vulnerabilities. This change landed in Chrome 138, which was promoted to Beta on May 28, 2025, and will become Stable on June 24, 2025.
This post details the impact of the HTML attribute escaping change on web developers and potential breakages; the security rationale behind this change is explained in our related post on the Security Engineering blog.
What changed
Suppose that you have a <div> element whose attribute data-content has a value of "<u>hello</u>". What happens when you read div.outerHTML?
Historically, you'd get the following HTML:
<div data-content="<u>hello</u>"></div>After the change, you'll get the following HTML:
<div data-content="<u>hello</u>"></div>Previously, neither < nor > were escaped in attributes. Now, both of these characters are always escaped.
What didn't change
The change exclusively modifies how HTML fragments are converted back into a string representation during serialization. The impact is limited to scenarios where the innerHTML or outerHTML properties are accessed, or when the getHTML() method is invoked on an element. These operations take the existing DOM structure and produce a textual HTML representation.
This change does not affect HTML parsing. Consider the following HTML:
<div id="div1" data-content="<u>hello</u>"></div> <div id="div2" data-content="<u>hello</u>"></div>Both divs will be parsed exactly the same way and in both cases div.dataset.content will return "<u>hello</u>".
What won't break?
If you use any DOM API, such as getAttribute, getAttributeNS, dataset, or attributes, to retrieve attribute values, they will return the same decoded values as before, specifically with < and > decoded.
Consider the following example, in which all console.log lines will log "<u>":
<div data-content="<u>"></div> const div = document.querySelector("div"); // All of the following will log "<u>" console.log(div.getAttribute("data-content")); console.log(div.dataset.content); console.log(div.attributes['data-content'].value);What can break?
innerHTML and outerHTML to get attributes
If you use innerHTML or outerHTML to extract the value of an attribute, your code can break. Consider the following, albeit slightly convoluted, example:
<div data-content="<u>"></div> const div = div.querySelector("div"); const content = div.outerHTML.match(/"([^"]+)"/)[1]; console.log(content);This code will exhibit different behavior after this change. Previously, content would've been equal to "<u>" but now it is "<u>".
Note that parsing HTML with regular expressions is not recommended. If you need to get a value of an attribute, use the DOM APIs described in previous sections.
End-to-end tests
If you have a CI/CD pipeline where you employ Chromium to generate HTML, and you've written tests to compare the HTML to a static expected value, these tests can break if any attribute contains < or >.
This is an expected breakage—you need to update the expected value so that all < and >characters are escaped to < and >, respectively.
Summary
This blog post described a change in the HTML specification which will lead browsers to start escaping < and > in attributes to improve security by preventing some instances of mutation XSS.
The change will be available for all users on June 24, 2025 on Chromium (version 138) and Firefox (version 140). It's also included in Safari 26 Beta which should be released around September 2025.
If you believe that this change broke your website and you don't have an easy way to fix it, please file a bug at https://issues.chromium.org/.
Additional information
Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2025-06-12 UTC.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-06-12 UTC."],[],[]]