Symfony 8.2 New Published on

A link in a translation, without raw

The trans_html filter replaces the pseudo-tags of a translation with HTML declared in the template, and escapes everything else, parameters included.

Topics covered: TranslationTwigSecurity

New in Symfony 8.2 (in development). To translate a sentence that contains a link, we put real HTML in the translation file, then displayed it with |raw. Three flaws: the address of the link is hard-coded in the translation, a translator can slip any HTML into it, and raw also turns off the escaping of the parameters. If one of them comes from the user, it is an XSS hole.

Pseudo-tags in the translation

The translation now only contains tags without attributes, which the template turns into HTML:

# translations/messages.en.yaml
invitation: 'Hello %name%! <login>Log in</login> to continue.'
{{ 'invitation'|trans_html(tags: {
    login: { tag: 'a', attr: { href: path('login') } },
}, arguments: { '%name%': name }) }}
  • A list of elements nests them, from the outermost to the innermost: [{ tag: 'strong' }, { tag: 'a', attr: {…} }].
  • Only the tags declared in tags become HTML. Everything else is escaped: the text, the parameters, the attribute values and any undeclared tag. Raw-text elements (script, style, iframe, noscript…) are refused.
  • A translation with badly closed tags is escaped entirely.
  • The result is already safe: no raw.

In PHP, for an email for example, the Symfony\Component\Translation\HtmlTranslator service does the same.

Your task

The home page invites visitors to log in or create an account, calling them by their name. The preview opens on /?name=<b>Ada</b>: the name shows up in bold, a sign that the HTML typed by the visitor goes through as is.

The shop is translated into English and French. Move the invitation to trans_html: pseudo-tags in both translations, links generated from the login and signup routes in the template, and no more raw. Put "create an account" in bold, on top of the link.

Where the feature comes from: the original pull request.