You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Safe Markup for HTML can cause XSS in attributes in the future #4996
Since #4991 it’s possible to create values that are safe for specific escaping strategies. This could suggest to the developer that proper escaping of that value in the template is now automatically taken care of securely. However this is not the case for the html strategy as this one is used in two different escaping contexts, one is as content of an element and the other is as an attribute value. Only attribute names (and unquoted attribute values) have their own strategy with html_attr.
This would enable XSS in some (not easy do notice) cases, like:
$value = new \Twig\Markup('<span title=" onload=alert(1) ">', 'UTF-8', ['html']);
{% autoescape'html' %}
<imgsrc="image.png"alt="{{ value }}">
{% endautoescape %}
This is very hard to notice for the template author IMO, and in this case both the PHP and the Twig side is fully valid and securely written but still results in XSS.
If we have more and more dynamic values marked as safe for the html strategy in the future we have to split this strategy up into two separate ones: One for HTML content and one for HTML attribute values.
I'm not sure I understand why this is related to #4991. It's a problem, but not a new one. "Safe for html" has always meant "this is an HTML fragment". Putting one inside an attribute has always been unsafe. Using |e('html_attr') "fixes" the issue, and that's the correct way (before and after the PR).
This is a tricky case indeed: quoted HTML values require the same escaping logic than text nodes (htmlspecialchars(?, ENT_QUOTES, 'UTF-8'), which is what makes it safe to rely on the html auto-escaping for normal cases (<a href="{{ path('home') }}">) for instance as long as you quote values.
However, an HTML fragment will need to be escaped there so that it stays an attribute value.
Using |e('html_attr') will escape that, but will also escape much more characters (which is safe for the output, but a lot slower). We would need a strategy that escapes the same than html but does not treat HTML fragments as safe, to allow such opt-in for cases where you want to support this.
As Twig has no knowledge of the structure of the HTML being generated (as it is not an HTML-only templating engine and cannot parse the Twig input as some kind of HTML markup as that would be a huge BC break being basically a different templating engine), we cannot do contextual auto-escaping where the auto-escaping changing the escaping strategy depending on where you render it.
…it is recommended to quote the HTML attribute value and use the html escaping strategy
I'm not sure I understand why this is related to #4991.
It is only partly related to that pull request, but before a general Markup value object was unsafe in many contexts so there was still a suggestion to the developer that this needs to be handled carefully. Now after the PR it looks like such an object is safe to use in templates without special care for escaping which is false and can cause security issues in the future I think.
We would need a strategy that escapes the same than html but does not treat HTML fragments as safe, to allow such opt-in for cases where you want to support this.
Finding the correct backwards compatible migration path for this issue is difficult I think. Adding a new strategy like html_attr_value would require every template author to switch to that without a reliably way to deprecate using the html strategy in attribute values. We could do it the other way round and add an html_fragment strategy but that would still require every author to explicitly use foo="{{ value|e('html') }}" if they use {% autoescape 'html_fragment' %}.
Activity
fabpot commentedon Oct 16, 2026
I'm not sure I understand why this is related to #4991. It's a problem, but not a new one. "Safe for html" has always meant "this is an HTML fragment". Putting one inside an attribute has always been unsafe. Using
|e('html_attr')"fixes" the issue, and that's the correct way (before and after the PR).stof commentedon Oct 16, 2026
This is a tricky case indeed: quoted HTML values require the same escaping logic than text nodes (
htmlspecialchars(?, ENT_QUOTES, 'UTF-8'), which is what makes it safe to rely on thehtmlauto-escaping for normal cases (<a href="{{ path('home') }}">) for instance as long as you quote values.However, an HTML fragment will need to be escaped there so that it stays an attribute value.
Using
|e('html_attr')will escape that, but will also escape much more characters (which is safe for the output, but a lot slower). We would need a strategy that escapes the same thanhtmlbut does not treat HTML fragments as safe, to allow such opt-in for cases where you want to support this.As Twig has no knowledge of the structure of the HTML being generated (as it is not an HTML-only templating engine and cannot parse the Twig input as some kind of HTML markup as that would be a huge BC break being basically a different templating engine), we cannot do contextual auto-escaping where the auto-escaping changing the escaping strategy depending on where you render it.
ausi commentedon Oct 16, 2026
This contradicts every template and example I know and also directly contradicts the Twig documentation in https://twig.symfony.com/doc/3.x/filters/escape.html#:~:text=HTML%20attributes%20values%20inside%20quotes :
Also https://twig.symfony.com/doc/3.x/filters/escape.html#:~:text=use%20the%20html%20escaping%20strategy :
It is only partly related to that pull request, but before a general
Markupvalue object was unsafe in many contexts so there was still a suggestion to the developer that this needs to be handled carefully. Now after the PR it looks like such an object is safe to use in templates without special care for escaping which is false and can cause security issues in the future I think.ausi commentedon Oct 16, 2026
Finding the correct backwards compatible migration path for this issue is difficult I think. Adding a new strategy like
html_attr_valuewould require every template author to switch to that without a reliably way to deprecate using thehtmlstrategy in attribute values. We could do it the other way round and add anhtml_fragmentstrategy but that would still require every author to explicitly usefoo="{{ value|e('html') }}"if they use{% autoescape 'html_fragment' %}.