Cross-site scripting (XSS)
Cross-site scripting (XSS) lets an attacker run their own JavaScript in your visitors' browsers; Drupal guards against it by escaping or filtering output.
Cross-site scripting (XSS) is an attack in which an attacker gets a website to run malicious code as though it were part of the site. In Drupal, it means script placed in content such as a comment or a page body, which other users then load in their browsers. Drupal's protections include Twig's automatic escaping, the Html::escape() and Xss::filter() helpers, and text formats.
How XSS is handled in Drupal 11
MDN describes the problem in two steps: a site accepts input an attacker can craft, then prints it into a page without making it harmless. The injected code can read the page content and local storage, and it can send HTTP requests with the visitor's credentials. Drupal gives you several layers that stop the second step.
- Twig. The Twig theme engine escapes everything it prints by default. The
rawfilter switches that off, and the Drupal documentation says to avoid it, particularly for data a user may have entered. Attributes must be quoted:class="{{ class }}"is safe,class={{ class }}is not. - Render arrays.
Renderer::ensureMarkupIsSafe()filters#markupwithXss::filter(). Unless you set#allowed_tags, it uses the admin tag list. Setting#plain_textescapes the text instead. - PHP helpers. Use
Html::escape()for plain text andXss::filter()for text that may carry some HTML. By default,Xss::filter()keepsa,em,strong,cite,blockquote,code,ul,ol,li,dl,dtanddd. It also removeson*event handler attributes andstyleattributes, and checks URL attributes for protocols such asjavascript:. - Translatable strings.
t()with@or%placeholders builds safe strings, and Drupal treats the result as safe when it renders it. - Text formats. Each text format is a series of filters that run on output; the stored text stays unchanged.
Example
A Twig template prints a value that a visitor may have typed. The first line is escaped by default. The second turns that protection off and is the pattern to avoid.
<p class="{{ label_class }}">{{ visitor_name }}</p>
<p>{{ visitor_name|raw }}</p>
In PHP, a render array that must show a user's text as plain text uses #plain_text:
$build = ['#plain_text' => $visitor_name];
Common mistakes
- Using
|rawon user data. It does not escape the value. - Leaving an attribute unquoted. An unquoted value can add new attributes to the tag.
- Using
Xss::filterAdmin()for visitor input. The API documentation limits it to admin-only content, because its tag list is much broader. It still excludesscriptandstyle. - Calling
check_markup()to sanitize text in a theme or module. The Drupal documentation says this is almost never correct, except for text areas that use a text format. - Giving a permissive text format to untrusted users. The format decides what survives on output.
For marketing and content teams
You rarely write code, but you decide who may use which text format. Keep the permissive formats for trusted editors and leave everyone else on a restrictive one. Install security updates promptly: the advisory list on drupal.org includes entries classified as cross-site scripting, in core and in contributed projects.