Prior to Drupal 8, we relied on manual escaping variables by our developers and themers. Every variable was either run through check_plain()
, filter_xss()
or another filter function manually or it was assumed to be safe. You can see where this assumption can easily be overlooked/forgotten and we end up with a security vulnerability usually in the form of an XSS attack.
One of the major decisions for choosing Twig in Drupal 8 was due to its autoescape security feature which all variables can be escaped automatically while they are printed. This alleviates a bunch of concerns and burden on the Security Team as well as protect Drupal Sites by default. This is equivalent to Drupal 7 writing <?php print check_plain($variable); ?>
on each variable printed.
Where as before Drupal 8 we were anticipating a variable was coming from an unsafe source and running it through a filtering function to ensure its output is safe for HTML. Now we no longer have to think about when or where to escape a variable as all variables are escaped on output in the template automatically while they are printed.
A new issue arises from this blessing of automatic safety. If you intend the variable's content to contain HTML it will now be escaped as HTML entities or double escaped if it was previously escaped. Before we needed to anticipate the unsafe variable now we have to react to unintentional escaping.
Instead of thinking about when to escape you now need to know when it's safe to mark a variable as safe.
Drupal and Twig each provide a couple of methods for telling the template the variable is safe. Carefully read the warnings above before using any of these methods. Note the subtle differences between the methods, and especially note that since Twig's current methods do not cover some important scenarios, they require extra caution.
Namely Twig_Markup()
object and |raw
filter. By passing a string variable wrapped in a Twig_Markup
class to the template, the autoescaping mechanism will avoid escaping it on print. Or by sending the variable through the |raw
filter in the template it will also print the raw unescaped string variable.
** Warning!** We highly discourage the use of |raw
in your templates because you could unwillingly open a security vulnerability if the source of that printed variable originates from an unsafe source like $_GET
for example. If you do use them, please thoroughly document why you think the variable is safe so others can incorporate your reasoning into their decisionmaking.
Drupal provides a helper class called SafeMarkup
or by building a renderable array. Also Drupal will mark output as safe if it's been through the t()
function as well as one the following SafeMarkup static methods:
SafeMarkup::checkPlain
SafeMarkup::escape
SafeMarkup::checkAdminXss
SafeMarkup::placeholder
And depending on the variables passed in being safe also:
SafeMarkup::format
SafeMarkup::setMultiple
Warning! We highly discourage the use of Drupal's SafeMarkup::set()
These methods could inadvertantly open a security vunerablilty if the source of that printed variable originates from an unsafe source such as $_REQUEST
. If you do use them, please thoroughly document why you think the variable is safe so others can incorporate your reasoning into their decisionmaking.
Knowing where a variable is coming from is the only way to know if it can be truly safe and that can be a difficult task sometimes while it travels through different Drupal subsystems. For that reason, we highly discourage marking a variable as safe further down the pipeline if it indeed needs to be unescaped markup.
So again, avoid at all costs using |raw
and SafeMarkup::set()
. And if you do use them please document thoroughly for others why you broke this rule and why you think the variable is safe.
Always look up the pipeline from the template to the preprocess to the render array that is building template and it's variables to find the top most place to mark a variable as safe to see where the source of the variable is truly coming from.
We attempted to use Twig_Markup
while enabled auto-escaping but there were many cases where wrapping a string in an object failed. When we try to concatenate two Safe strings wrapped in objects they would cease to be safe even if both Strings were originally safe. Also many variables were used as keys as well and array keys are not allowed to be Objects in PHP. To work around these we decided to make strings safe by storing them in an array of known safe strings. Still concatenation will break their safety but we can now return strings after we've stored them as safe and no longer have to deal with problems of strings wrapped as objects as is Twig_Markup
.
One thing to note with this approach, NEVER store an opening unsafe tag as safe without closing it or an unsafe character as you can unintentionally make a security vulnerability this way.
- First and foremost, put markup in a template if at all possible. That's where it belongs, avoid writing markup in PHP, this includes avoid writing theme functions as well.
- Use
SafeMarkup::format
or#type => inline_template
renderable array if you deem it to much of a burden to create a new template. - Filter your variables through
SafeMarkup::checkAdminXss()
orXss::filterAdmin()
if you need a limited HTML set and for admin only. OrXss::filter
to reduce the set of tags.