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 div
s 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/.