Problem/Motivation

In Drupal 8 we enabled twig autoescape but we also created SafeMarkup::checkPlain() to escape text and then mark it as safe to ensure that Twig does not double escape it.

Proposed resolution

We should rely on Twig auto escape and completely remove SafeMarkup::checkPlain(). SafeMarkup::checkPlain() is dangerous because if the resulting string is modified in any way, for example nl2br(), the result will no longer be marked safe. In places where explicit escaping is needed, Html::escape() should be used.

A common pattern is to use:

[
  #markup => $value, 
  '#safe_strategy' => Markup::SAFE_STRATEGY_ESCAPE
]

To allow the render system to do the escaping for us.

Remaining tasks

  • Agree that this is desired
  • Manual testing
  • Review
  • Commit

Manual testing steps

(From #40)

A few places to check (because checking the whole patch would be crazy):

  • Twig Debug Comments (must turn on twig debugging in your services.yml)
  • Site name in Bartik (in header)
  • Views token replacement
  • A view_mode with an & in the views UI entity view mode choices
  • Views exposed form options.
  • User names with special characters.
  • User Permissions Page
  • Quick Edit tool
  • Language Content Settings table
  • Config Translation Overview
  • Image Style edit form
  • Comment Author name & title

And anywhere else in your travels, feel free to use simplytest.me for this task. And ask me if you need clarification on those pointer areas. Try to including tags and & in input to see what you get. and looking for where it's encoded in source as & which will show up on the screen as &

User interface changes

None

API changes

Use Html::escape where explicit escaping is still required.

Data model changes

None

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because we have double escaping in HEAD because of incorrect SafeMarkup::checkPlain() usage
Issue priority Major because SafeMarkup::checkPlain() is hard to use correctly and we should be using auto escape
Disruption Not disruptive for contrib or custom because deprecated and removal will be dealt with later see #2555473: Deprecate SafeMarkup::checkPlain() for Drupal 8.0.x
CommentFileSizeAuthor
#183 2545972-2-183.patch79.86 KBalexpott
#183 182-183-interdiff.txt2.83 KBalexpott
#182 interdiff.txt3.11 KBWim Leers
#182 2545972-2-180.patch82.51 KBWim Leers
#177 2545972-2-177.patch81.83 KBalexpott
#177 175-177-interdiff.txt5.53 KBalexpott
#177 2545972-2-177.patch81.83 KBalexpott
#177 175-177-interdiff.txt5.53 KBalexpott
#175 2545972-2-175.patch79.98 KBalexpott
#175 174-175-interdiff.txt10.63 KBalexpott
#174 2545972-2-174.patch64.96 KBalexpott
#174 171-174-interdiff.txt19.32 KBalexpott
#171 2545972-2-171.patch48.18 KBalexpott
#171 165-171-interdiff.txt601 bytesalexpott
#165 2545972-2-165.patch47.88 KBalexpott
#165 162-165-interdiff.txt681 bytesalexpott
#164 2568517-164.patch9.7 KBalexpott
#164 162-164-interdiff.txt681 bytesalexpott
#162 2545972-2-162.patch47.21 KBalexpott
#162 160-162-interdiff.txt530 bytesalexpott
#160 2545972-2-160.patch47.21 KBalexpott
#151 2545972.151.patch138 KBalexpott
#144 interdiff-140-144.txt4.1 KBstefan.r
#144 2545972-144.patch174.34 KBstefan.r
#140 interdiff-136-140.txt33.19 KBstefan.r
#140 2545972-140-fail.patch178.94 KBstefan.r
#137 remove_all_usages-2545972-136.patch180.99 KBstar-szr
#130 2545972.130.patch181.81 KBalexpott
#126 2545972.126.patch197.44 KBalexpott
#112 2545972.112.patch285.45 KBalexpott
#112 90-112-interdiff.txt9.07 KBalexpott
#91 Screen Shot 2015-08-21 at 18.48.19.png48.91 KBWim Leers
#91 Screen Shot 2015-08-21 at 18.46.07.png24.14 KBWim Leers
#90 2545972.90.patch280.4 KBalexpott
#90 86-90-interdiff.txt1.26 KBalexpott
#86 2545972.86.patch278.96 KBalexpott
#86 80-86-interdiff.txt3.76 KBalexpott
#80 2545972.80.patch282.26 KBalexpott
#80 78-80-interdiff.txt1.3 KBalexpott
#78 2545972.77.patch282.21 KBalexpott
#78 74-77-interdiff.txt4.59 KBalexpott
#74 2545972.74.patch277.87 KBalexpott
#74 72-74-interdiff.txt4.46 KBalexpott
#72 2545972.72.patch276.61 KBalexpott
#72 65-72-interdiff.txt15.66 KBalexpott
#65 2545972.65.patch279.79 KBalexpott
#65 61-65-interdiff.txt3.86 KBalexpott
#61 60-61-interdiff.txt25.29 KBalexpott
#61 2545972.61.patch278.07 KBalexpott
#60 2545972.57.patch273.07 KBalexpott
#60 57-interdiff.txt133.45 KBalexpott
#54 pp_1_remove-2545972-54.patch260.67 KBjoelpittet
#53 2545972.53-temp-label-do-not-test.patch471 bytesericjenkins
#53 2545972.53.png23.1 KBericjenkins
#52 2545972.52.patch266.18 KBericjenkins
#49 2545972.49.patch265.98 KBericjenkins
#32 2545972.32.patch265.82 KBalexpott
#32 29-32-interdiff.txt1.91 KBalexpott
#29 2545972.29.patch265.48 KBalexpott
#29 19-29-interdiff.txt3.22 KBalexpott
#23 create-2545972-23.patch268.32 KBjoelpittet
#23 interdiff.txt2.1 KBjoelpittet
#21 create-2545972-21.patch267.39 KBjoelpittet
#21 interdiff.txt1.16 KBjoelpittet
#19 2545972.19.patch267.1 KBalexpott
#19 15-19-interdiff.txt1.04 KBalexpott
#15 2545972.15.patch266.86 KBalexpott
#4 2545972.4.patch2.59 KBalexpott
#4 2-4-interdiff.txt628 bytesalexpott
#2 2545972.2.patch1.98 KBalexpott
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

alexpott created an issue. See original summary.

alexpott’s picture

Status: Active » Needs review
FileSize
1.98 KB
catch’s picture

Looks right to me, agreed we need this - function body is identical to check_plain() in Drupal 7, and it's worth having the wrapper in case we ever needed to adjust those arguments again.

alexpott’s picture

Let's convert one particularly egregious usages of SafeMarkup::checkPlain() in this patch. UrlHelper::filterBadProtocol() really ought not to be setting safe strings - it's only used in Xss::attributes() - which should not be marking anything as safe.

UrlHelper::filterBadProtocol() has existing test coverage in Drupal\Tests\Component\Utility\UrlHelperTest

alexpott’s picture

Category: Task » Bug report

I think due to #4 this should now be categorised as a bug.

star-szr’s picture

Title: Create Html::encodeEntities() so we have a non SafeMarkup version of SafeMarkup::checkPllain() » Create Html::encodeEntities() so we have a non SafeMarkup version of SafeMarkup::checkPlain()
Status: Needs review » Reviewed & tested by the community

I agree we should have this. Patch looks solid and even has test coverage, thanks!

Wim Leers’s picture

  1. +++ b/core/lib/Drupal/Component/Utility/Html.php
    +++ b/core/lib/Drupal/Component/Utility/Html.php
    @@ -348,4 +348,22 @@ public static function decodeEntities($text) {
    
    @@ -348,4 +348,22 @@ public static function decodeEntities($text) {
    +  public static function encodeEntities($text) {
    

    Symmetry! :)

  2. +++ b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
    @@ -259,4 +259,36 @@ public function providerDecodeEntities() {
    +    return array(
    +      array('Drupal', 'Drupal'),
    +      array('&lt;script&gt;', '<script>'),
    +      array('&amp;lt;script&amp;gt;', '&lt;script&gt;'),
    +      array('&amp;#34;', '&#34;'),
    +      array('&quot;', '"'),
    +      array('&amp;quot;', '&quot;'),
    +      array('&#039;', "'"),
    +      array('&amp;#039;', '&#039;'),
    +      array('©', '©'),
    +      array('→', '→'),
    +      array('➼', '➼'),
    +      array('€', '€'),
    +    );
    

    Nit: Could've used [] instead of array().

xjm’s picture

Status: Reviewed & tested by the community » Needs review

Hm, I really feel like this is bloat. We are wrapping a PHP function. Every single method we add to the string sanitization API makes it harder to understand and people are already overwhelmed by it. See all the discussion in #2506195: Remove SafeMarkup::set() from Xss::filter().

The reason checkPlain() exists in the first place was a unicode support bug prior to PHP 5.2.x, no? D7 including check_plain() was basically just BC. In D8 we've retained it even though we renamed it... and now we're going to add yet another copy of it, so the caller has to choose between two different wrappers for a PHP function?

xjm’s picture

And yes, to @alexpott's point, I also think decodeEntities() is probably bloat. But at least it's not bloat in what is already an overwhelming space.

xjm’s picture

I also disagree with the assertion that this is a bug, because the bad code in head should just use htmlspecialchars() instead.

alexpott’s picture

Assigned: Unassigned » catch
Category: Bug report » Task

For me the question comes how much to do we want to see htmlspecialchars() all over the place and have to remember the args. And happens when one of those does not match the expectations set by html::decodeEntities() or the SafeMarkup escaping.

Filed #2546232: Remove SafeMarkup::checkPlain() from UrlHelper to address the bug put in this with the current recommended solution and setting back to a task and assigning to @catch as this was his suggestion.

pwolanin’s picture

It looks as though we could (maybe *should*) call ini_set("default_charset", 'UTF-8') in DrupalKernel. Then we could omit the 3rd argument in usages since it defaults to UTF-8 in PHP 5.5 and to the default charset in 5.6+.

The only risk would be someone putting the wrong second argument (or no argument) in a case like HTML attribute values where we rely on all quotes being escaped.

alexpott’s picture

There was discussion about the use of htmlspecialchars() on #2505701: Document SafeMarkup::set and Use htmlspecialchars() directly in Attribute() so we don't bloat the list of safe strings see comments 11 and 12.

Was hmm okay at the time but now looking what's likely to be thrown up by #2527360: Review all usages of Xss::filter(), Xss::filterAdmin(), and Html::escape() I think a method makes sense. Especially as SafeMarkup::checkPlain() actually makes no sense outside of SafeMarkup::format().

catch’s picture

Every single method we add to the string sanitization API makes it harder to understand and people are already overwhelmed by it.

If we have Html::encodeEntities() that provides a place to document where it's used. Calls to htmlspecialchars() with two optional arguments don't provide anywhere for this clarification to happen.

For me the question comes how much to do we want to see htmlspecialchars() all over the place and have to remember the args.

Yes it comes down to this, and also whether those arguments to htmlspecialchars() might ever have to change in the future - as #12 suggests they could (although at least no bc break for now).

alexpott’s picture

Or we can do the proper thing and remove SafeMarkup::checkPlain(). The patch attached reveals numerous problems with our use of SafeMarkup::checkPlain().

Some examples of currently wrong usage:

  • $href = '<' . SafeMarkup::checkPlain($attributes['href']) . '>;';
  • $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(SafeMarkup::checkPlain($htaccess_lines)));
  • $context['message'] = 'Updating ' . SafeMarkup::checkPlain($module) . ' module';
  • $import[] = '@import url("' . SafeMarkup::checkPlain(file_create_url($next_css_asset['data']) . '?' . $query_string) . '");';
  • '#suffix' => SafeMarkup::checkPlain($comment->label()) . '</li>'
  • '#markup' => '<span lang="en">' . SafeMarkup::checkPlain($source_array[0]) . '</span>',
  • $options[$vocabulary->id()][$term->id()] = str_repeat('-', $term->depth) . SafeMarkup::checkPlain($term->getName());

By removing SafeMarkup::checkPlain() we begin to fulfil the promise of Twig's auto escape feature AND we make double escaping far less likely.

alexpott’s picture

Another example of how SafeMarkup::checkPlain() makes things difficult #2503963: XSS in Quick Edit: entity title is not safely encoded

alexpott’s picture

+++ b/core/modules/views_ui/src/Tests/XssTest.php
@@ -23,14 +23,14 @@ class XssTest extends UITestBase {
   public function testViewsUi() {
     $this->drupalGet('admin/structure/views');
-    $this->assertRaw('&lt;script&gt;alert(&quot;foo&quot;);&lt;/script&gt;, &lt;marquee&gt;test&lt;/marquee&gt;', 'The view tag is properly escaped.');
+    $this->assertEscaped('<script>alert("foo");</script>, <marquee>test</marquee>', 'The view tag is properly escaped.');
 
     $this->drupalGet('admin/structure/views/view/sa_contrib_2013_035');
-    $this->assertRaw('&amp;lt;marquee&amp;gt;test&amp;lt;/marquee&amp;gt;', 'Field admin label is properly escaped.');
+    $this->assertEscaped('<marquee>test</marquee>', 'Field admin label is properly escaped.');
 
     $this->drupalGet('admin/structure/views/nojs/handler/sa_contrib_2013_035/page_1/header/area');
-    $this->assertRaw('[title] == &amp;lt;marquee&amp;gt;test&amp;lt;/marquee&amp;gt;', 'Token label is properly escaped.');
-    $this->assertRaw('[title_1] == &amp;lt;script&amp;gt;alert(&amp;quot;XSS&amp;quot;)&amp;lt;/script&amp;gt;', 'Token label is properly escaped.');
+    $this->assertEscaped('[title] == <marquee>test</marquee>', 'Token label is properly escaped.');
+    $this->assertEscaped('[title_1] == <script>alert("XSS")</script>', 'Token label is properly escaped.');
   }

The old code here is basically asserting that everything is double escaped.

Status: Needs review » Needs work

The last submitted patch, 15: 2545972.15.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
1.04 KB
267.1 KB

One of the failing tests

Status: Needs review » Needs work

The last submitted patch, 19: 2545972.19.patch, failed testing.

joelpittet’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
1.16 KB
267.39 KB

This should knock off one more of those the Drupal\views\Tests\Plugin\DisplayFeedTest.

To note, this is currently broken currently broken in HEAD because if a field value has a new line character, the SafeMarkup'd string is now dirty when the <br> tags are added it will be passed to the link and won't match the SafeMarkup::safe_strings list, thus escaping it.

I'm not sure my solution is the right one, but at least it will do the trick for the time being.

Really like the direction of this patch though, big fan!

Status: Needs review » Needs work

The last submitted patch, 21: create-2545972-21.patch, failed testing.

joelpittet’s picture

Status: Needs work » Needs review
FileSize
2.1 KB
268.32 KB

Well that sucks, fixed one test, broke 4 more:S @alexpott you set traps! I'm not so sure the search title's should be escaping those values? @see interdiff The UUID was just a casting problem.

Status: Needs review » Needs work

The last submitted patch, 23: create-2545972-23.patch, failed testing.

dawehner’s picture

It is odd to have Html::encodeEntities vs. SafeMarkup::checkPlain, so can we implement a a SafeMarkup::encodeEntities calling to ::checkPlain or wise versa?

alexpott’s picture

+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php
@@ -155,7 +156,7 @@ public function viewElements(FieldItemListInterface $items) {
-    return nl2br(Html::encodeEntities($item->value));
+    return SafeString::create(nl2br(Html::encodeEntities($item->value)));

So this fix is interesting! Because this means the the original code...

    // The text value has no text format assigned to it, so the user input
    // should equal the output, including newlines.
    return nl2br(SafeMarkup::checkPlain($item->value));

Is broken in some instances because the nl2br will cause the string to lose safe-ness.

Also this fix will reopen the debate about SafeString::create() and whether it should be @internal. However SafeString::create() + Html::encodeEntities() is better than SafeMarkup::checkPlain() because it lacks any side effects. I will investigate if there is another fix.

alexpott’s picture

@dawehner the latest patches are completely removing SafeMarkup::checkPlain()

dawehner’s picture

Title: Create Html::encodeEntities() so we have a non SafeMarkup version of SafeMarkup::checkPlain() » Replace Safemarkup::checkplain() and rely more on twig autoescaping

Meh, so I reviewed the other issues for fun, meh

alexpott’s picture

Status: Needs work » Needs review
Issue tags: +Needs issue summary update
FileSize
3.22 KB
265.48 KB

So a non safe-string solution it possible just means doing more rendering (at some point we probably need to discuss whether this is desirable but I guess render cache and all the other caching efforts saves us here).

Patch is rerolled on top some recent commits that have removing some SafeMarkup::checkPlain() calls.

The issue summary needs a major update. Also I think we should open another issue to remove SafeMarkup::escape() that is postponed on this one.

alexpott’s picture

Oh and patch in #29 improves the assertEscaped to both look for the escaped text (which it did) AND ensure the raw text is not there.

Status: Needs review » Needs work

The last submitted patch, 29: 2545972.29.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
1.91 KB
265.82 KB

So the improvement to assertEscaped was ill-advised I guess we can explore that in a followup as it caused fails in Drupal\system\Tests\Menu\BreadcrumbTest and Drupal\field\Tests\FormTest and is not directly related to this issue - I only added it because of the improvements made to Drupal\views\Tests\SearchIntegrationTest by this patch.

Last test fail fixed too.

alexpott’s picture

Category: Task » Bug report
Priority: Normal » Major
Issue summary: View changes

Reclassifying as a bug since this patch resolves quite a bit of double escaping and removes a lot of unnecessary strings from the SafeMarkup safe list.

@joelpittet I didn't understand

Tracking this down was a bit like inception x10 levels on the dream stack.

From the issue summary so removed it.

alexpott’s picture

Issue summary: View changes
alexpott’s picture

Assigned: catch » Unassigned
Issue tags: -Needs issue summary update +Needs change record

Opened #2549393: Remove SafeMarkup::escape() and rely more on Twig autoescaping and unassigned @catch since this is now ready for anyone to review.

alexpott’s picture

joelpittet’s picture

Whoops that was part of a comment that ended in the issue summary related to tracking down the views feed test failure in stringformatter.

Regarding more rendering, we'd like to avoid early rendering as much as possible, but in this case it may already be rendered... I'll have to keep an eye out in a review.

alexpott’s picture

@joelpittet I didn't say "early rendering" - I said "more rendering" this patch does not add any early rendering.

joelpittet’s picture

@alexpott sorry was on phone checking email, hadn't read the patch yet and read them as the same thing. Fewf

joelpittet’s picture

Issue tags: +Needs manual testing

I'll add a test tonight at the airport for the bug found in #21 which should be resolved now.

I'm adding a "Needs manual testing" because with this change there is a possibility that we double escaped a few things by accident.
A few places to check (because checking the whole patch would be crazy):

  1. Twig Debug Comments (must turn on twig debugging in your services.yml)
  2. Site name in Bartik (in header)
  3. Views token replacement
  4. A view_mode with an & in the views UI entity view mode choices
  5. Views exposed form options.
  6. User names with special characters.
  7. User Permissions Page
  8. Quick Edit tool
  9. Language Content Settings table
  10. Config Translation Overview
  11. Image Style edit form
  12. Comment Author name & title

And anywhere else in your travels, feel free to use simplytest.me for this task. And ask me if you need clarification on those pointer areas. Try to including <tag>tags and & in input to see what you get. and looking for where it's encoded in source as &amp;amp; which will show up on the screen as &amp;

dawehner’s picture

Note to myself, stopped before views.

  1. +++ b/core/lib/Drupal/Core/Entity/EntityListBuilder.php
    @@ -112,10 +111,10 @@ protected function getEntityIds() {
       protected function getLabel(EntityInterface $entity) {
    -    return SafeMarkup::checkPlain($entity->label());
    +    return $entity->label();
       }
    

    +1 for not filtering/escaping here

  2. +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionLoggingSubscriber.php
    @@ -45,7 +45,7 @@ public function __construct(LoggerChannelFactoryInterface $logger) {
    -    $this->logger->get('access denied')->warning(SafeMarkup::checkPlain($request->getRequestUri()));
    +    $this->logger->get('access denied')->warning(Html::encodeEntities($request->getRequestUri()));
    
    @@ -56,7 +56,7 @@ public function on403(GetResponseForExceptionEvent $event) {
    -    $this->logger->get('page not found')->warning(SafeMarkup::checkPlain($request->getRequestUri()));
    +    $this->logger->get('page not found')->warning(Html::encodeEntities($request->getRequestUri()));
    
    +++ b/core/lib/Drupal/Core/EventSubscriber/Fast404ExceptionHtmlSubscriber.php
    @@ -79,7 +79,7 @@ public function on404(GetResponseForExceptionEvent $event) {
         if ($config->get('fast_404.enabled') && $exclude_paths && !preg_match($exclude_paths, $request->getPathInfo())) {
    ...
    +        $fast_404_html = strtr($config->get('fast_404.html'), ['@path' => Html::encodeEntities($request->getUri())]);
             $response = new Response($fast_404_html, Response::HTTP_NOT_FOUND);
    

    Mh, don't we want to just escape on output time?

  3. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php
    @@ -33,7 +33,7 @@ public function viewElements(FieldItemListInterface $items) {
    -          '#markup' => SafeMarkup::checkPlain($entity->id()),
    +          '#markup' => Html::encodeEntities($entity->id()),
    

    Do we really need to Html::encodeEntities what is passed into #markup? Just curious here

  4. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php
    @@ -132,7 +132,7 @@ public function viewElements(FieldItemListInterface $items) {
    -          '#title' => $string,
    +          '#title' => ['#markup' => $string],
    

    This is odd I mean do we really need that? Maybe we could add some small comment?

  5. +++ b/core/modules/action/action.views_execution.inc
    @@ -5,14 +5,14 @@
    -use Drupal\Component\Utility\SafeMarkup;
    +use Drupal\Component\Utility\Html;
    ...
    -  $select_all_placeholder = SafeMarkup::checkPlain('<!--action-bulk-form-select-all-->');
    +  $select_all_placeholder = Html::encodeEntities('<!--action-bulk-form-select-all-->');
    

    Why do we need to escape a known string?

  6. +++ b/core/modules/block/src/BlockListBuilder.php
    @@ -261,7 +260,7 @@ protected function buildBlocksForm() {
    -            '#markup' => SafeMarkup::checkPlain($info['label']),
    +            '#markup' => Html::encodeEntities($info['label']),
    

    So there are places in the patch which use #markup with Html::encodeEntities and without. I think we should make it clear, which one is the one to use

  7. +++ b/core/modules/block/src/Controller/BlockLibraryController.php
    @@ -109,7 +108,7 @@ public function listBlocks(Request $request, $theme) {
    -      $row['category']['data'] = SafeMarkup::checkPlain($plugin_definition['category']);
    +      $row['category']['data'] = $plugin_definition['category'];
    

    If I understand template_preprocess_table() correctly it will then be passed to $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content which is a variable which will be escaped by twig

  8. +++ b/core/modules/block/src/Controller/CategoryAutocompleteController.php
    @@ -59,7 +59,7 @@ public function autocomplete(Request $request) {
    -        $matches[] = array('value' => $category, 'label' => SafeMarkup::checkPlain($category));
    +        $matches[] = array('value' => $category, 'label' => Html::encodeEntities($category));
    

    Sounds like a classical usecase where we should apply render side escaping ... This is used in autocomplete.js:renderItem

  9. +++ b/core/modules/comment/comment.tokens.inc
    @@ -161,7 +161,7 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
    -          $replacements[$original] = $sanitize ? SafeMarkup::checkPlain($comment->language()->getId()) : $comment->language()->getId();
    +          $replacements[$original] = $sanitize ? Html::encodeEntities($comment->language()->getId()) : $comment->language()->getId();
    

    So just as example I guess the entire resulting token string will be marked as safe then?

  10. +++ b/core/modules/editor/src/EditorXssFilter/Standard.php
    @@ -114,7 +113,7 @@ protected static function filterXssDataAttributes($html) {
             // value. There is no need to explicitly decode $node->value, since the
             // DOMAttr::value getter returns the decoded value.
             $value = Xss::filterAdmin($node->value);
    -        $node->value = SafeMarkup::checkPlain($value);
    +        $node->value = Html::encodeEntities($value);
    

    It is interesting that we filter first and then still encode the entities

  11. +++ b/core/modules/editor/src/Tests/EditorSecurityTest.php
    @@ -388,7 +387,6 @@ function testSwitchingSecurity() {
         //  - switch to every other text format/editor
         //  - assert the XSS-filtered values that we get from the server
    -    $value_original_attribute = SafeMarkup::checkPlain(self::$sampleContent);
         $this->drupalLogin($this->privilegedUser);
    

    Just went back in time and indeed this was never used

  12. +++ b/core/modules/field_ui/src/FieldStorageConfigListBuilder.php
    @@ -116,16 +117,10 @@ public function buildRow(EntityInterface $field_storage) {
    -    $usage_escaped = '';
    -    $separator = '';
    -    foreach ($usage as $usage_item) {
    -      $usage_escaped .=  $separator . SafeMarkup::escape($usage_item);
    -      $separator = ', ';
    -    }
    -    $row['data']['usage'] = SafeMarkup::set($usage_escaped);
    +    $row['data']['usage'] = SafeMarkup::set(implode($usage, ', '));
    

    Nice, this is quite better.

  13. +++ b/core/modules/node/src/NodeTypeForm.php
    @@ -55,7 +54,7 @@ public function form(array $form, FormStateInterface $form_state) {
    -      $form['#title'] = SafeMarkup::checkPlain($this->t('Add content type'));
    +      $form['#title'] = $this->t('Add content type');
           $fields = $this->entityManager->getBaseFieldDefinitions('node');
    

    Nice one!

  14. +++ b/core/modules/node/src/Plugin/Search/NodeSearch.php
    @@ -335,7 +336,7 @@ protected function prepareResults(StatementInterface $found) {
             $this->renderer->renderPlain($build) . ' ' .
    -        SafeMarkup::escape($this->moduleHandler->invoke('comment', 'node_update_index', array($node, $item->langcode)))
    +        $this->moduleHandler->invoke('comment', 'node_update_index', array($node, $item->langcode))
    

    So the result of comment_node_update_index() is safe because we use render API there?

  15. +++ b/core/modules/node/src/Plugin/views/row/Rss.php
    @@ -78,7 +77,7 @@ public function buildOptionsForm_summary_options() {
       public function summaryTitle() {
         $options = $this->buildOptionsForm_summary_options();
    -    return SafeMarkup::checkPlain($options[$this->options['view_mode']]);
    +    return $options[$this->options['view_mode']];
       }
    

    This should be fine, as the result is rendered as Link using the LinkGenerator at the end

  16. +++ b/core/modules/rdf/rdf.module
    @@ -416,7 +416,7 @@ function rdf_preprocess_username(&$variables) {
    -    $variables['attributes']['content'] = SafeMarkup::checkPlain($variables['name_raw']);
    +    $variables['attributes']['content'] = $variables['name_raw'];
    

    So AttributeValueBase/AttributeString escape key and value so we are safe here

  17. +++ b/core/modules/shortcut/src/Form/SwitchShortcutSet.php
    @@ -71,7 +70,7 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter
         $options = array_map(function (ShortcutSet $set) {
    -      return SafeMarkup::checkPlain($set->label());
    +      return $set->label();
         }, $this->shortcutSetStorage->loadMultiple());
    

    Btw. it is really great that you no longer have to escape for #type select options. This is quite a DX win

  18. +++ b/core/modules/taxonomy/taxonomy.tokens.inc
    @@ -131,14 +131,14 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
    -          $replacements[$original] = SafeMarkup::checkPlain($vocabulary->label());
    +          $replacements[$original] = Html::encodeEntities($vocabulary->label());
    ...
    -            $replacements[$original] = SafeMarkup::checkPlain($parent->getName());
    +            $replacements[$original] = Html::encodeEntities($parent->getName());
    

    Out of scope of this issue but I guess those instances should use $sanitize ? as well?

tim.plunkett’s picture

Title: Replace Safemarkup::checkplain() and rely more on twig autoescaping » Replace Safemarkup::checkPlain() and rely more on twig autoescaping

Wow EntityListBuilder::getLabel() is now completely pointless :)

mbrett5062’s picture

Title: Replace Safemarkup::checkPlain() and rely more on twig autoescaping » Remove Safemarkup::checkPlain() and rely more on twig autoescaping

Changed issue title from "Replace" to "Remove" as per #27

dawehner’s picture

+++ b/core/modules/views/views.module
@@ -66,8 +66,8 @@ function views_views_pre_render($view) {
-          'view_args' => SafeMarkup::checkPlain(implode('/', $view->args)),
-          'view_path' => SafeMarkup::checkPlain(Url::fromRoute('<current>')->toString()),
+          'view_args' => implode('/', $view->args),
+          'view_path' => Url::fromRoute('<current>')->toString(),

Mh, so these variables are used in JS, does that mean we need some protected there instead?

Wim Leers’s picture

alexpott’s picture

Title: Remove Safemarkup::checkPlain() and rely more on twig autoescaping » [PP-1] Remove Safemarkup::checkPlain() and rely more on twig autoescaping
Status: Needs review » Postponed
Issue tags: -Needs change record +blocker, +Needs change record updates

Since this in a chain I'll postpone in on #2549791: Remove SafeMarkup::xssFilter() and provide ability to define #markup escaping strategy and what tags to filter. I basically wrote all these patches first to prove that it was actually possible to do. Now that that is proved I'll adjust the issue statuses accordingly.

catch’s picture

I opened #2550945: Add Html::escape() to talk about adding Html::encodeEntities() or not. I think the wrapper is useful both in the interim state before this overall patch gets committed and after.

ericjenkins’s picture

I'm sprinting at the Midwest Developers Summit in Chicago right now, and I'm taking a look at #40.

ericjenkins’s picture

Re-rolled #32. Fixed conflicting lines in core/themes/engines/twig/twig.engine, preferring #32 lines over changes made elsewhere.

ericjenkins’s picture

I'm no longer working on this ticket. Planning to pick it up tomorrow.

ericjenkins’s picture

Patch requires another re-roll. Addressing this now.

ericjenkins’s picture

Fixed merge conflict in core/modules/node/src/Plugin/Search/NodeSearch.php, favoring Patch #32 line 338 over conflicting line. Re-rolled.

ericjenkins’s picture

Updating the Teaser label in the core.entity_view_mode.node.teaser.yml file to include an '&' character causes double escaping as a result of Patch #52. Picture:

2545972.53.png



Steps to reproduce

  1. Apply Patch #52
  2. Apply 2545972.53-temp-label-do-not-test.patch
  3. Re-install Drupal
  4. Navigate to: admin/structure/views/view/frontpage

I'm done working on this issue for the day.

joelpittet’s picture

Status: Postponed » Needs review
FileSize
260.67 KB

Here's a re-roll and this is no longer postponed, but it does need the review @ericjenkins did in #53 taken into consideration and after that is resolved, more manual testing.

Which is cases where the escaped value gets passed into link generator.

joelpittet’s picture

Title: [PP-1] Remove Safemarkup::checkPlain() and rely more on twig autoescaping » Remove Safemarkup::checkPlain() and rely more on twig autoescaping
Status: Needs review » Needs work

The last submitted patch, 54: pp_1_remove-2545972-54.patch, failed testing.

Wim Leers’s picture

keopx’s picture

Title: Remove Safemarkup::checkPlain() and rely more on twig autoescaping » Remove SafeMarkup::checkPlain() and rely more on twig autoescaping
Wim Leers’s picture

Title: Remove SafeMarkup::checkPlain() and rely more on twig autoescaping » Remove SafeMarkup::checkPlain() and rely more on Twig autoescaping
alexpott’s picture

Status: Needs work » Needs review
FileSize
133.45 KB
273.07 KB

Here's a massive reroll based on #32 and with all SafeMarkup::checkPlain() references in code removed. Also used the new Html::escape() instead of Html::encodeEntities() that the original patch added. Still need to handle all the feedback in the issue. I'll start addressing that once this is green again.

The interdiff is the result of the removal of all the checkPlain in comments and cleaning up the re-roll. Unfortunately it is not that useful.

alexpott’s picture

Now let's use the new #safe_strategy.

Status: Needs review » Needs work

The last submitted patch, 61: 2545972.61.patch, failed testing.

mpdonadio’s picture

Issue summary: View changes

Updated IS to match approach in #60.

dawehner’s picture

Note #41 is still true (mostly)

alexpott’s picture

Status: Needs work » Needs review
FileSize
3.86 KB
279.79 KB

Fixing the test fails... now to address all the feedback since finally the patch is up-to-date with head.

Wim Leers’s picture

Status: Needs review » Needs work

#63++


#60: interdiff review:

  1. +++ b/core/includes/form.inc
    @@ -568,10 +568,11 @@ function template_preprocess_form_element_label(&$variables) {
    - * \Drupal\Component\Utility\SafeMarkup::checkPlain() or
    - * \Drupal\Component\Utility\Xss::filter(). Furthermore, if the batch operation
    ...
    + * \Drupal\Component\Utility\SafeMarkup::format(),
    + * \Drupal\Component\Utility\Xss::filter(), or rely on the auto-escaping of the
    

    This docs update looks a bit strange. Why are we explicitly recommending SafeMarkup::format() here, while we were not before?

    And while that makes some sense, I don't think the mention of Xss::filter() still makes sense; I think that should point to #markup's #safe_strategy property?

  2. +++ b/core/includes/form.inc
    @@ -595,8 +596,9 @@ function template_preprocess_form_element_label(&$variables) {
    + *   // Twig will auto-escape this.
    + *   $context['message'] = $node->label();
    

    Why explicitly mention it here? We don't do that elsewhere? Specifically because this is the node title and that'll cause people to raise eyebrows when they read this?

  3. +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
    @@ -15,9 +15,9 @@
    + * markup strings created from @link theme_render render arrays @endlink via
    + * drupal_render().
    

    Pre-existing technically, but reading markup strings created from render arrays are automatically marked safe sounds scary. If it said markup strings created from render arrays are automatically filtered/escaped as necessary and then marked as safe or something like that, that'd be less scary :)

  4. +++ b/core/lib/Drupal/Core/Controller/TitleResolverInterface.php
    @@ -18,10 +18,10 @@
    -   * implementation should call \Drupal\Component\Utility\SafeMarkup::checkPlain()
    -   * or \Drupal\Component\Utility\Xss::filterAdmin() on the string, or use
    -   * appropriate placeholders to sanitize dynamic content inside a localized
    -   * string before returning it. The title may contain HTML such as EM tags.
    +   * implementation should call \Drupal\Component\Utility\Xss::filterAdmin() on
    +   * the string, or use appropriate placeholders to sanitize dynamic content
    +   * inside a localized string before returning it. The title may contain HTML
    +   * such as EM tags.
    

    Shouldn't this instead be recommending to use ['#markup' => 'title', '#allowed_tags' => […]]?

  5. +++ b/core/lib/Drupal/Core/Field/AllowedTagsXssTrait.php
    @@ -23,7 +23,7 @@
        * Used for items entered by administrators, like field descriptions, allowed
        * values, where some (mainly inline) mark-up may be desired (so
    -   * \Drupal\Component\Utility\SafeMarkup::checkPlain() is not acceptable).
    +   * \Drupal\Component\Utility\Html::escape() is not acceptable).
    

    Shouldn't this also mention #markup + #allowed_tags?

  6. +++ b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
    @@ -55,7 +55,7 @@ public function dynamicTitle() {
    +   *   Whether or not to mark the title as safe using the render system.
    

    s/the render system/the Markup element's "escape" safe strategy/

  7. +++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
    @@ -180,18 +180,18 @@ public function testReplace($search, $replace, $subject, $expected, $is_safe) {
    +    // SafeMarkup::escape() will escape the markup tag since the string is not
    

    s/markup tag/tag/

  8. +++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
    @@ -180,18 +180,18 @@ public function testReplace($search, $replace, $subject, $expected, $is_safe) {
    +    // marked.
    

    s/marked/marked safe/

#61: interdiff review:

+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -321,7 +322,7 @@ public function render() {
-      $build['#markup'] = Html::escape($build['#markup']);
+      $build['#safe_strategy'] = Markup::SAFE_STRATEGY_ESCAPE;

+++ b/core/modules/views/src/Plugin/views/display/Feed.php
@@ -99,7 +99,8 @@ public function preview() {
-        '#markup' => Html::escape(drupal_render_root($output)),
+        '#markup' => drupal_render_root($output),
+        '#safe_strategy' => Markup::SAFE_STRATEGY_ESCAPE,

This one is wrong. drupal_render_root() already explicitly returns a safe string, and we specifically still want to escape it because we want to preview the source (markup) of the feed.

Could use specific documentation explaining this probably.

This would also fix 3 of the 4 failures AFAICT.


Next: another complete review.

Wim Leers’s picture

#65:

+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -322,6 +322,8 @@ public function render() {
+      // Cause #markup to lose its SafeStringInterface object wrapper.
+      $build['#markup'] = (string) $build['#markup'];
       $build['#safe_strategy'] = Markup::SAFE_STRATEGY_ESCAPE;

+++ b/core/modules/views/src/Plugin/views/display/Feed.php
@@ -99,7 +99,9 @@ public function preview() {
-        '#markup' => drupal_render_root($output),
+        // Cast the result of rendering to a string so it loses its
+        // SafeStringInterface object wrapper.
+        '#markup' => (string) drupal_render_root($output),
         '#safe_strategy' => Markup::SAFE_STRATEGY_ESCAPE,

Hah, that's definitely an alternative solution to the problem I also found in my review of #61 in my previous comment. But I think it is much clearer to use Html::escape(), because that's more explicit.

alexpott’s picture

@Wim Leers the problem with using Html::escape() is that you will then XSS admin filter the for no reason.

alexpott’s picture

Issue summary: View changes
Wim Leers’s picture

Chatted with Alex Pott about #67/#68. It doesn't make sense to use Html::escape(), because that doesn't mark the result as a safe string. Which means that #markup will still attempt to do filtering. Which is excessive work. Hence, the approach that #65 uses already is the best option.

I do think the comment can be made clearer though, IMO the intent is only clear from context and would be better spelled out explicitly. i.e.

+        // Cast the result of rendering to a string so it loses its
+        // SafeStringInterface object wrapper.

->

+        // Cast the result of rendering to a string so it loses its
+        // SafeStringInterface object wrapper. Do this so that the #safe_strategy takes effect and
+        // escapes the markup: unlike most things this intends to display the actual markup.

(Or something like that.)

If you disagree that that's better, it's fine to leave it as is.

Wim Leers’s picture

And finally, the full review of #65, as promised.

  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BasicStringFormatter.php
    @@ -37,7 +37,7 @@ public function viewElements(FieldItemListInterface $items) {
    -      $elements[$delta] = array('#markup' => nl2br(SafeMarkup::checkPlain($item->value)));
    +      $elements[$delta] = array('#markup' => nl2br(Html::escape($item->value)));
    

    It's a pre-existing problem, but this will cause Markup's filtering safe strategy to still be applied. Even though that's absolutely unnecessary.

    The only tag that is going to be in there is <br>, by definition. And that is allowed by \Drupal\Component\Utility\Xss::getAdminTagList().

    However, using SafeString::create() here or creating another SafeStringInterface implementation for this also feels kinda overkill.

    Not sure what's best.

    But if we don't improve this here, I think we should at least document it, because in the future we won't be scrutinizing string safeness etc quite as much again.

  2. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php
    @@ -132,7 +132,7 @@ public function viewElements(FieldItemListInterface $items) {
    -          '#title' => $string,
    +          '#title' => ['#markup' => $string],
    

    Why? $string is already escaped.

  3. +++ b/core/modules/block/src/Controller/CategoryAutocompleteController.php
    @@ -59,7 +59,7 @@ public function autocomplete(Request $request) {
    -        $matches[] = array('value' => $category, 'label' => SafeMarkup::checkPlain($category));
    +        $matches[] = array('value' => $category, 'label' => Html::escape($category));
    
    +++ b/core/modules/block/tests/src/Unit/CategoryAutocompleteTest.php
    @@ -48,7 +48,7 @@ protected function setUp() {
    -      return array('value' => $suggestion, 'label' => SafeMarkup::checkPlain($suggestion));
    +      return array('value' => $suggestion, 'label' => Html::escape($suggestion));
    

    In #2503963: XSS in Quick Edit: entity title is not safely encoded, it was determined that this escaping actually belongs on the client side.

  4. +++ b/core/modules/content_translation/content_translation.admin.inc
    --- a/core/modules/dblog/src/Controller/DbLogController.php
    +++ b/core/modules/dblog/src/Controller/DbLogController.php
    

    Unused uses: SafeMarkup & Xss.

  5. +++ b/core/modules/editor/src/Tests/EditorSecurityTest.php
    @@ -388,7 +387,6 @@ function testSwitchingSecurity() {
    -    $value_original_attribute = SafeMarkup::checkPlain(self::$sampleContent);
    

    LOL, yes, that was dead code for some time already. Yay for no more dragging it along.

  6. +++ b/core/modules/field_ui/src/FieldStorageConfigListBuilder.php
    @@ -116,16 +117,10 @@ public function buildRow(EntityInterface $field_storage) {
    -        $usage[] = $this->bundles[$entity_type_id][$bundle]['label'];
    +        $usage[] = Html::escape($this->bundles[$entity_type_id][$bundle]['label']);
           }
         }
    -    $usage_escaped = '';
    -    $separator = '';
    -    foreach ($usage as $usage_item) {
    -      $usage_escaped .=  $separator . SafeMarkup::escape($usage_item);
    -      $separator = ', ';
    -    }
    -    $row['data']['usage'] = SafeMarkup::set($usage_escaped);
    +    $row['data']['usage'] = SafeMarkup::set(implode($usage, ', '));
    

    Much nicer :)

  7. +++ b/core/modules/filter/filter.module
    @@ -616,8 +615,8 @@ function _filter_url_parse_full_links($match) {
    -  $caption = SafeMarkup::checkPlain(_filter_url_trim($match[$i]));
    -  $match[$i] = SafeMarkup::checkPlain($match[$i]);
    +  $caption = Html::escape(_filter_url_trim($match[$i]));
    +  $match[$i] = Html::escape($match[$i]);
    
    @@ -631,8 +630,8 @@ function _filter_url_parse_email_links($match) {
    -  $caption = SafeMarkup::checkPlain(_filter_url_trim($match[$i]));
    -  $match[$i] = SafeMarkup::checkPlain($match[$i]);
    +  $caption = Html::escape(_filter_url_trim($match[$i]));
    +  $match[$i] = Html::escape($match[$i]);
    

    Lots of side effects removed (needless additions to the safe markup list), yay!

  8. +++ b/core/modules/filter/src/FilterFormatFormBase.php
    @@ -79,7 +79,7 @@ public function form(array $form, FormStateInterface $form_state) {
    -      '#options' => array_map('\Drupal\Component\Utility\SafeMarkup::checkPlain', user_role_names()),
    +      '#options' => user_role_names(),
           '#disabled' => $is_fallback,
           '#weight' => -10,
         );
    +++ b/core/modules/filter/src/FilterFormatListBuilder.php
    +++ b/core/modules/filter/src/FilterFormatListBuilder.php
    @@ -107,7 +107,7 @@ public function buildRow(EntityInterface $entity) {
         }
         else {
           $row['label'] = $this->getLabel($entity);
    -      $roles = array_map('\Drupal\Component\Utility\SafeMarkup::checkPlain', filter_get_roles_by_format($entity));
    +      $roles = filter_get_roles_by_format($entity);
    

    Hm. These #options changes are in many places. Can you explain why this is okay? Where are they being escaped now?

  9. +++ b/core/modules/image/src/Form/ImageStyleEditForm.php
    @@ -9,10 +9,11 @@
    +use Drupal\Component\Utility\Html;
    

    Unused.

  10. +++ b/core/modules/locale/src/Form/TranslateEditForm.php
    @@ -73,7 +73,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    -            '#markup' => '<span lang="en">' . SafeMarkup::checkPlain($source_array[0]) . '</span>',
    +            '#markup' => '<span lang="en">' . Html::escape($source_array[0]) . '</span>',
               );
             }
             else {
    @@ -82,13 +82,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
               $original_singular = [
                 '#type' => 'item',
                 '#title' => $this->t('Singular form'),
    -            '#markup' => '<span lang="en">' . SafeMarkup::checkPlain($source_array[0]) . '</span>',
    +            '#markup' => '<span lang="en">' . Html::escape($source_array[0]) . '</span>',
                 '#prefix' => '<span class="visually-hidden">' . $this->t('Source string (@language)', array('@language' => $this->t('Built-in English'))) . '</span>',
               ];
               $original_plural = [
                 '#type' => 'item',
                 '#title' => $this->t('Plural form'),
    -            '#markup' => '<span lang="en">' . SafeMarkup::checkPlain($source_array[1]) . '</span>',
    +            '#markup' => '<span lang="en">' . Html::escape($source_array[1]) . '</span>',
    

    Can't these be updated to have the <span>s in #prefix & #suffix?

  11. +++ b/core/modules/node/src/Controller/NodePreviewController.php
    @@ -61,7 +60,7 @@ public function view(EntityInterface $node_preview, $view_mode_id = 'full', $lan
       public function title(EntityInterface $node_preview) {
    -    return SafeMarkup::checkPlain($this->entityManager->getTranslationFromContext($node_preview)->label());
    +    return $this->entityManager->getTranslationFromContext($node_preview)->label();
       }
    

    <3

  12. +++ b/core/modules/node/src/Plugin/Search/NodeSearch.php
    @@ -448,7 +448,7 @@ protected function indexNode(NodeInterface $node) {
           $rendered = $this->renderer->renderPlain($build);
     
    -      $text = '<h1>' . SafeMarkup::checkPlain($node->label($language->getId())) . '</h1>' . $rendered;
    +      $text = '<h1>' . Html::escape($node->label($language->getId())) . '</h1>' . $rendered;
    

    This could be implemented more cleanly by doing:

    $build['search_title'] = [
      '#prefix' => '<h1>',
      '#markup' => $node->label(), '#safe_strategy' => Markup::SAFE_STRATEGY_ESCAPE
      '#suffix' => '</h1>',
    ];
    $text = $this->renderer->renderPlain($build);
    
  13. +++ b/core/modules/rdf/rdf.module
    +++ b/core/modules/rdf/rdf.module
    @@ -419,7 +419,7 @@ function rdf_preprocess_username(&$variables) {
       // Long usernames are truncated by template_preprocess_username(). Store the
       // full name in the content attribute so it can be extracted in RDFa.
       if ($variables['truncated']) {
    -    $variables['attributes']['content'] = SafeMarkup::checkPlain($variables['name_raw']);
    +    $variables['attributes']['content'] = $variables['name_raw'];
    

    SafeMarkup is still imported.

  14. +++ b/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module
    @@ -9,6 +9,8 @@
    +use Drupal\Component\Utility\SafeMarkup;
    +
    

    Unused.

  15. +++ b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
    @@ -55,16 +55,24 @@ public function dynamicTitle() {
    +   * @todo What is mark_safe doing here now?
    

    Isn't it effectively testing the same thing? A title that still needs to be escaped (and is not yet marked safe) and a title that already is escaped (and already is marked safe)?

    I don't see the difference.

  16. +++ b/core/modules/tracker/src/Controller/TrackerUserTab.php
    @@ -28,6 +27,6 @@ public function getContent(UserInterface $user) {
       public function getTitle(UserInterface $user) {
    -    return SafeMarkup::checkPlain($user->getUsername());
    +    return $user->getUsername();
       }
    

    <3

  17. +++ b/core/modules/user/src/Tests/Views/AccessPermissionTest.php
    @@ -35,7 +35,7 @@ function testAccessPerm() {
    -    $this->assertEqual($access_plugin->pluginTitle(), t('Permission'));
    +    $this->assertEqual((string) $access_plugin->pluginTitle(), t('Permission'));
    

    This already uses assertEqual(), so why is this change necessary?

  18. +++ b/core/modules/user/tests/src/Unit/Views/Argument/RolesRidTest.php
    @@ -7,7 +7,7 @@
    +use Drupal\Component\Utility\Html;
    

    Unused.

  19. +++ b/core/modules/views/src/Plugin/views/PluginBase.php
    @@ -365,7 +365,8 @@ protected function viewsTokenReplace($text, $tokens) {
    +        // Twig will auto-escape any encoded HTML entities.
    +        $twig_tokens[$token] = Html::decodeEntities($replacement);
    

    Does this need additional test coverage?

  20. +++ b/core/modules/views/src/Tests/SearchIntegrationTest.php
    @@ -7,6 +7,7 @@
    +use Drupal\Component\Utility\Html;
    

    Unused.

alexpott’s picture

Status: Needs work » Needs review
FileSize
15.66 KB
276.61 KB

So started at #41 - thanks @dawehner

  1. Yep
  2. Fixed - this is really context.
  3. Fixed - using #safe_strategy
  4. This is just to make the filtering consistent regardless of whether we are wrapping in a URL. I've jiggled the code to make it more obvious.
  5. well we could put the escaped string here but it is easier to read and search for <!--action-bulk-form-select-all-->
  6. This uses '#safe_strategy' => Markup::SAFE_STRATEGY_ESCAPE now. The render element Markup contains documentation as to how it escapes or filters text.
  7. Yep - trying to do late escaping as much as possible
  8. Let's address this in a followup - I agree we should be escaping as late as possible.
  9. Whatever is using the tokens needs to deal with safeness... by passing in the sanitise param. Polluting the safe markup list with token values makes no sense since by their very definition they are destined to become parts of larger strings.
  10. Yep it is interesting... not much to do with this patch though
  11. :) you Marty McFly
  12. :)
  13. :)
  14. This was dealt with in #2501757: Remove SafeMarkup::set in NodeSearch::prepareResults() - no longer changed by this patch.
  15. Yep
  16. Yep
  17. Yep
  18. And yep - but yeah out-of-scope.

re #44 - thanks @dawehner again - I shouldn't have removed that. Adding back escaping. Escaping and JS should be a separate issue.

Still to address #53 - thanks for finding this @ericjenkins

#60 - thanks @Wim Leers

  1. Well because if you want to have these things contain markup then you need to use something to mark it safe. I've completely rewritten the docs since Xss::filter is not going to help either.
  2. It's documentation that relates to and helps people understand the point made above.
  3. Improved the documentation
  4. Rewrote the docs
  5. Lets handle that in #2551511: Remove SafeMarkup::set() from AllowedTagsXssTrait
  6. Fixed
  7. Fixed
  8. Fixed

re #70 - thanks @Wim Leers (again!) - fixed this by using your better comment.

#71 - thanks @Wim Leers (again!)

  1. Yeah this is tricky. Here's where #2554073: Allow #markup to be an array of strings and join them safely could be used to explode this on newlines and then just set the strategy to escape - which would be really nice a self documenting. Not done anything for this one yet.
  2. See answer to #41.4
  3. I think we should deal with any JS side things in a followup - to me that is out-of-scope.
  4. Fixed
  5. :)
  6. :)
  7. :)
  8. By twig - I'll add a test for this. Or check if we already have one.
  9. Fixed
  10. Sure and then we can use #safe_strategy
  11. :)
  12. I don't see the point in the additional render here.
  13. Fixed
  14. Fixed
  15. That was a comment made to self whilst developing... removed.
  16. :)
  17. This is because we're now passing the TranslationWrapper all the way through to Twig. If we don't do the string cast we get:
    Exception Warning    TestBase.php       657 Drupal\simpletest\TestBase->assertE
        var_export does not handle circular referencesvar_export(Object, 1)
        Drupal\simpletest\TestBase->assertEqual(Object, 'Permission')
        Drupal\user\Tests\Views\AccessPermissionTest->testAccessPerm()
        Drupal\simpletest\TestBase->run(Array)
        simpletest_script_run_one_test('28',
        'Drupal\user\Tests\Views\AccessPermissionTest::testAccessPerm')
    

    I've added a comment. TranslationWrappers are not as simple as SafeStrings.

  18. Fixed
  19. Nope we have good test coverage and improving coverage in #2546210: Views subtokens are broken since the introduction of inline templates for replacements
  20. Fixed
Wim Leers’s picture

Awesome reroll, Alex, thanks!

  • #41.3's fix is missing
  • #41.8: can we open that follow-up already, and track the ones that were pointed out here? Such an issue will be much easier if we already have a list of things that should do this. Especially if they have // @todo: … in the code. Also related to this: #44, #71.3.
  • #71.1: okay, curious how we're going to end up resolving that one :)
  • #71.8: AFAICT that test is not yet present? (Or perhaps it's not in the interdiff?)
  • #71.12: there's no additional rendering in my proposal. All I'm proposing is to not first do rendering, and then still concatenate something with the result. But, instead, add another child to the render array with a low enough weight (I see I forgot to mention that: it should have '#weight' => -1000). Then, when we render, there's no more need for us to prefix the rendered output with <h1>TITLE</h1> — the rendering already took care of that for us, and we just get a single safe string!

Interdiff review:

  1. +++ b/core/includes/form.inc
    @@ -566,13 +566,13 @@ function template_preprocess_form_element_label(&$variables) {
    + * \Drupal\Core\StringTranslation\TranslationManager::translate. If they are not
    

    s/translate/translate()/

  2. +++ b/core/modules/user/src/Tests/Views/AccessPermissionTest.php
    @@ -35,6 +35,8 @@ function testAccessPerm() {
    +    // The plugin title will be a tranlsation wrapper, cast to a string to test
    

    s/tranlsation/translation/
    :)


So, the only remaining bits:

  • #41.3
  • #53
  • #71.1
  • #71.8
  • #71.12
alexpott’s picture

  • 41.3's fix occurred in a previous re-roll
  • 41.8 Thinking some more about this. I think it is to late to do this. It is a pretty significant change and existing JS will be made insecure
  • 71.1 Well it is fixed in this patch apart from the fact it is unnecessarily admin filtered.
  • 71.8 Yep no test yet.
  • 71.12 I see - fixed.

#73

  1. Fixed
  2. Fixed

re #53 - fixed. The problem was that EntityRow was doing some unnecessary escaping - Twig'll do that for us! Added test coverage.

So still to do:

  • Decide whether we'll do anything out #71.1 - this might be best to have a followup to add a test.
  • Find a test for or add a test for #71.8
Wim Leers’s picture

  • #74 looks splendid!
  • #41.8: Ok, so we basically draw the line at "D7-like features still do server-side escaping" then. That's good enough for now I guess.
  • #71.1: +1 to follow-up — this issue is blocking a critical, and that pre-existing problem shouldn't make it block further.
  • I do think a test for #71.8 is important, because that's rather opaque right now.

So the only remaining thing is then #71.8.

Wim Leers’s picture

Status: Needs review » Needs work

The last submitted patch, 74: 2545972.74.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
4.59 KB
282.21 KB

Fixed the tests...

So I was wrong about #71.8 - by default checkbox and radio values are admin filtered because we use #title to print them out. I think this is the wrong behaviour because it does not really meet the expectations set out with auto-escape. We have two options:

  • Either: Continue to escape everything and accept that we're going to needlessly filter everything.
  • Or: Change the behaviour to escape them.

The patch attached goes for the latter since in discussion with @Wim Leers we think it makes the most sense. Let's see if anything breaks.

Status: Needs review » Needs work

The last submitted patch, 78: 2545972.77.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
1.3 KB
282.26 KB

Seems I broke some tests too :)

Wim Leers’s picture

I think this is ready.

We still need that manual testing though — see #40 for steps.

xjm’s picture

Issue summary: View changes
xjm’s picture

So FWIW, I'm tentatively in favor of making this change, despite the BC break it causes, because (a) it will be a clean break (so easy to find and fix) and (b) it has significant performance, security, DX, etc. implications.

However, I do think we need to have a discussion about the change with the committers before we commit this one, because it is so sweeping.

Also, has anyone looked over what other change records might need to be updated? It'd be good to have a plan for that as well. (We should search all the CRs for check_plain and checkPlain, etc.)

alexpott’s picture

Here's the current CR situation:

dawehner’s picture

So FWIW, I'm tentatively in favor of making this change, despite the BC break it causes, because (a) it will be a clean break (so easy to find and fix) and (b) it has significant performance, security, DX, etc. implications.

Right if we break BC we should break it hard!

alexpott’s picture

Title: Remove SafeMarkup::checkPlain() and rely more on Twig autoescaping » Remove all usages SafeMarkup::checkPlain() and rely more on Twig autoescaping
FileSize
3.76 KB
278.96 KB

Discussed with catch and he suggested that we don't do the removal in this issue. It has enough to do without doing that. Also considering how widely used this is in contrib it would be good to have a release with the method deprecated and give time for everyone to convert their modules. Here's a patch which keeps SafeMarkup::checkPlain() and it's tests but removes all usages from core.

This is also a reroll.

Wim Leers’s picture

Also considering how widely used this is in contrib it would be good to have a release with the method deprecated and give time for everyone to convert their modules.

Shouldn't this patch then mark it for deprecation? (Presumably in beta 16, so that during beta 15, developers can upgrade.)

Status: Needs review » Needs work

The last submitted patch, 86: 2545972.86.patch, failed testing.

alexpott’s picture

Issue summary: View changes

@catch suggested doing this in another issue - so we can get this in a discuss the deprecation in another issue - this one #2555473: Deprecate SafeMarkup::checkPlain() for Drupal 8.0.x

alexpott’s picture

Status: Needs work » Needs review
FileSize
1.26 KB
280.4 KB

One of the new SafeMarkup::set() patches added a weird unit test.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs manual testing
FileSize
24.14 KB
48.91 KB

Patch is green. Before RTBC'ing, we still needed that round of manual testing described in #40. None of the exact steps showed problems, but in the process, I did find two double escaping bugs, which are actually the same bug: the Frontpage view's title has a double-escaped site name, this shows up on the actual frontpage, but also in the preview.

However… this is a pre-existing bug in HEAD!

Hence: RTBC. (I think a patch needs to be RTBC for a framework & release manager to even look at the patch? I could be wrong. Feel free to un-RTBC if I'm wrong.)

Wim Leers’s picture

catch’s picture

Assigned: Unassigned » xjm

So I suggested deferring the deprecation and removal to another issue, so that we could get the main patch in without a full discussion on contrib impact. That should mean we can remove both tags from this issue (apart from it needing to be reviewed by a committer), but assigning to xjm since she added the tags to confirm that was the only thing that needed a wider discussion.

joelpittet’s picture

The underlying problem with #53 seems to be if you filter or escape a known safe value and pass it in to the link generator, it will escape it again. The filter one is pre-existing in head because we removed the SafeMarkup::set() safe from it in a recent issue.

I'm glad we removed the premature escaping, and in most cases may be the correct course of action but I feel that part will pop it's head up again.

So as far as I see, removing the premature escaping is great fix but only treats the symptoms. If the solution is from:

\Drupal::l(SafeMarkup::checkPlain('&>'), '<none>');
\Drupal::l(SafeMarkup::filterXssAdmin('&>'), '<none>');

to

\Drupal::l(SafeString::create(Html::escape('&>')), '<none>');
\Drupal::l(SafeString::create(Xss::filterAdmin('&>')), '<none>');

I think we should add that to the change record so people know how to deal with it? If that is not correct and I'm way off base, then the correct way to deal with those pre-escaped variables would be nice to cover in the CR regardless as it seem to cause me and others a bit of confusion.

pwolanin’s picture

I'm a little confused by this flag

+      '#safe_strategy' => Markup::SAFE_STRATEGY_ESCAPE,

Can we define a different element like #plain_text instead of overloading #markup?

If not can we make the constant at least shorter? like Markup::ESCAPE

joelpittet’s picture

Status: Reviewed & tested by the community » Needs review

As much as I love this issue, #94 needs to be addressed and if we can't I'd even propose reverting #2506195: Remove SafeMarkup::set() from Xss::filter() until it can be.

Edit: wrong referenced thing to revert

The last submitted patch, 49: 2545972.49.patch, failed testing.

The last submitted patch, 52: 2545972.52.patch, failed testing.

pwolanin’s picture

@joelpittet

part of your question doesn't make sense. Since l() escapes the title, I think you'd never do this:

\Drupal::l(SafeString::create(Html::escape('&>')), '<none>');

Rather you'd just do:

\Drupal::l('&>', '<none>');

For the other you'd use a render array like:

\Drupal::l(['#markup' => '&>'], '<none>');

However, per my comment in #96, I don't like overloading #markup for plain text also.

joelpittet’s picture

My cases are assuming you have markup you don't want to double escape and have previously sanitized.

It does make sense because the fact that l() will escape unsafe strings and since they aren't marked safe any more to prevent the escaping in l(), they will be double escaped now.

So passing a render array to the title is another approach that maps to xss admin filter if not safe. Will prevent double escaping but may do xss filter twice.

Damien Tournoud’s picture

I agree with @pwolanin. '#safe_strategy' => Markup::SAFE_STRATEGY_ESCAPE is just plain wrong, as it mixes two radically different issues: the issue of the format (plain text vs. html), and the issue of the sanitization.

This looks fine to me:

[ '#plain_text' => '&>' ]

Unfortunately, the Markup element is hardcoded inside \Drupal\Core\Render\Renderer so it is the only one that can omit #type right now.

joelpittet’s picture

The examples are contrived to be succinct. Imagine those variables were filter/escaped into a variable and passed in.

alexpott’s picture

#94 @joelpittet for Drupal::l() and wanting to pass markup use #markup, if you want it escaped just do what @pwolanin says in #100. If what you are passing to Drupal::l() has already been rendered then just pass it in - it will be a SafeString already. If it has already been escaped or filtered and its not already rendered you're doing it wrong.

As for the objection about overloading #markup with plain text. This issue does absolutely nothing to change that.. This issue converts [#markup => SafeMarkup::checkPlain($something)] to use a later escaping strategy. I'm intrigued by the idea of introducing #plain_text and removing the #safe_strategy key. But there might be a key benefit of the current approach that is being overlooked. It makes it simpler to change the strategy whilst rendering. For example, at the moment node titles are escaped. However it is not uncommon to what to change this an permit a limited set of tags in the title. In HEAD atm the moment you could just change the strategy to Markup::SAFE_STRATEGY_FILTER and set #allowed_tags to the desired list.

@pwolanin I'm not too fussed about the constant names and would be happy to review an issue suggesting the change but one advantage with the longer name is that it tells you what it is for.

pwolanin’s picture

Possibly we should just go ahead here, but I think the DX is a bit crummy and we should have a follow-up to improve it.

In terms of omitting #type I think it could be ok to let the markup type handle both #markup and #plain_text. Basically instead of passing a #safe_strategy just use a different key name.

alexpott’s picture

Created #2555931: Add #plain_text to escape text in render arrays - if we are going to do that then I'd like to get that one done before this.

joelpittet’s picture

Sorry for raising alarm bells, I am only trying to make sure people who run into double escaping after this change lands that they will know how to "not do it wrong". And that we add that to the change record so they can get support and pointers in the right direction.

So we are recommending people (obviously I know), let the Drupal::l() escape the text passed in and avoid pre-escaping unsafe markup with SafeMarkup::checkPlain() or it's replacement Html::escape() if they were doing that before. Alternatively, pass in ['#markup' => $string_with_markup] if you want to filter out Xss but you are fine with or expecting un-escaped(no xss) tags and characters. If the value has previously run Html::escape() from somewhere else in the system, then you (a: might be doing it wrong? b: should pass it through ['#markup' => $pre_escaped]?. And we are not recommending SafeString::create()'s usage for known pre-escaped strings?

I have no opinion on the #plain_text comment because I don't understand it's use yet, though a follow-up seems like a likely candidate considering this patch's size. I'll follow the follow-up to try and get a grasp on it.

xjm’s picture

I agree with #102 (regarding the problems of conflating escaping with filtering), #105 (that the DX for this whole space is definitely "crummy"), and #107 (that we need the change records and other documentation to communicate what people actually need to do. These problems are not introduced by this patch, though. The one change is that this patch does make a much wider use of the #safe_strategy introduced in #2549791: Remove SafeMarkup::xssFilter() and provide ability to define #markup escaping strategy and what tags to filter, which in turn was addressing limitations of the change made in #2273925: Ensure #markup is XSS escaped in Renderer::doRender(), which happened in response to the original SafeMarkup API needing to conflate escaped and filtered text in the first place, all of which put a lot more on the semantics of #markup.

So I think we definitely need to address those issues in followups. I do think the change record updates probably need to block this though. I'm planning to review the patch here (either as a reviewer or committer if it gets RTBCed again by someone else) and I'll see if I can help with that.

pwolanin’s picture

Status: Needs review » Postponed
Related issues: +#2555931: Add #plain_text to escape text in render arrays

From #106, maybe this should be marked postponed on #2555931: Add #plain_text to escape text in render arrays

alexpott’s picture

Here an issue that replaced all usages of SafeMarkup::checkPlain() in a module with a relatively complex UI - https://www.drupal.org/node/2555045#new

xjm’s picture

+++ b/core/includes/common.inc
@@ -281,10 +280,10 @@ function valid_email_address($mail) {
 function check_url($uri) {
-  return SafeMarkup::checkPlain(UrlHelper::stripDangerousProtocols($uri));
+  return Html::escape(UrlHelper::stripDangerousProtocols($uri));
 }

So, this is interesting in light of the concerns above about URL escaping.

I thought "Huh, surprised that's not a method on the URL class now," and then t turns out check_url() is kind of redundant with UrlHelper::filterBadProtocol()... but for an entity decode that's in the method but not the function. This means (I think) that those two functions would have slightly different results on a URL that was passed in as http://www.example.com?a=1&amp;b=2. (@alexpott also pointed out that the case where the decodeEntities() would matter isn't actually in the unit tests.) Regardless, I think that check_url() should be deprecated in a followup to just become a wrapper for filterBadProtocol().

There are places that check_url() is used with @ tokens for t() in HEAD that could result in double-escaping following the removal from the safe list of checkPlain() results for the URLs. @alexpott is updating the patch for this. @todo file followup.

alexpott’s picture

Status: Postponed » Needs review
FileSize
9.07 KB
285.45 KB

Nice catch wrt to check_url(). I think we need to deprecate this function too. It can be replaced by UrlHelper::stripDangerousProtocols() and UrlHelper::filterBadProtocol(). UrlHelper::stripDangerousProtocols() should be use when the result can be escaped by SafeMarkup::format() or Twig. UrlHelper::filterBadProtocol() should be used when you are sanitizing for other reasons - for example tokens.

Setting back to needs review since I now think this can go in parallel to #2555931: Add #plain_text to escape text in render arrays - that issue is likely to go in first but if this one does get in first then that one just gets larger.

xjm’s picture

(Just taking notes during review.)

+++ b/core/includes/file.inc
@@ -371,7 +371,7 @@ function file_save_htaccess($directory, $private = TRUE, $force_overwrite = FALS
-    $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(SafeMarkup::checkPlain($htaccess_lines)));
+    $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(Html::escape($htaccess_lines)));

This is entertaining. The lines for $htaccess_lines are statically defined in code in FileStorage::htaccessLines(). The purpose of the escaping here is not to sanitize user input, but actually to encode the angle brackets in the example .htaccess for display to the user inside a code tag. I think this may be a double-escaping bug in HEAD regardless of whether a ! or @ placeholder is used (which would be a regression introduced by #2399037: String::format() marks a resulting string as safe even when passed an unsafe passthrough argument). Related: #2506445: Replace !placeholder with @placeholder in t() and format_string() for non-URLs in tests (which in its current form also leaves this bug there). This one is out of the scope of the patch and the patch does not affect it.

Status: Needs review » Needs work

The last submitted patch, 112: 2545972.112.patch, failed testing.

xjm’s picture

  1. +++ b/core/includes/form.inc
    @@ -566,12 +566,13 @@ function template_preprocess_form_element_label(&$variables) {
    - * 'error_message' could contain any user input, it is the responsibility of
    - * the code calling batch_set() to sanitize them first with a function like
    - * \Drupal\Component\Utility\SafeMarkup::checkPlain() or
    - * \Drupal\Component\Utility\Xss::filter(). Furthermore, if the batch operation
    - * returns any user input in the 'results' or 'message' keys of $context, it
    - * must also sanitize them first.
    + * 'error_message' could contain any HTML that you want to display then, it is
    + * the responsibility of the code calling batch_set() to mark them safe using a
    + * function like \Drupal\Component\Utility\SafeMarkup::format() or
    + * \Drupal\Core\StringTranslation\TranslationManager::translate(). If they are
    + * not marked safe then Twig will auto-escape them. Furthermore, if the batch
    + * operation returns any HTML in the 'results' or 'message' keys of $context,
    + * it must also consider sanitization.
    

    So, bit confusing, but turns out the topic documentation for the Batch API is in form.inc. Cool...

    This updated documentation is a bit unclear to me and I had to read it several times and discuss with @alexpott to understand. The change is that previously, passing unsanitized text could result in XSS, whereas now, passing any HTML in the messages will result in escaping unless the strings are in the safe list...

    ...Or so the updated comment claims, but actually, I think that at least parts of it will not get escaped by Twig. See in _batch_process_page(). The 'init_message' and 'error_message' get added in JS so it's not clear to me they would actually be escaped by Twig. Related: #2503963: XSS in Quick Edit: entity title is not safely encoded

    It's not uniform either; there's a fallback case where 'error_message' does get passed to renderBarePage() in a #markup which eventually goes through renderRoot() and therefore gets XSS-filtered in that case but not obviously in the other.

    @alexpott also found the use of the 'message' key in _batch_do() and it is similarly put into a JSON response, so therefore has the same escaping concern.

    Per @alexpott the 'results' key in the $context array is used by the specific implementation, so the comment might hold true for that one. Regardless, at least part of this docs change is incorrect and could result in XSS vulnerabilities in batch implementations if they try to follow this documentation.

    Also, the updated documentation is a bit confusing and switches between passive voice and second person, but we can worry about that once we make it say the right thing.

  2. +++ b/core/includes/form.inc
    @@ -595,8 +596,9 @@ function template_preprocess_form_element_label(&$variables) {
    - *   $context['results'][] = $node->id() . ' : ' . SafeMarkup::checkPlain($node->label());
    - *   $context['message'] = SafeMarkup::checkPlain($node->label());
    + *   // Twig will auto-escape these variables.
    + *   $context['results'][] = $node->id() . ' : ' .$node->label();
    + *   $context['message'] = $node->label();
    
    @@ -615,10 +617,10 @@ function template_preprocess_form_element_label(&$variables) {
    - *     $context['results'][] = $row->id . ' : ' . SafeMarkup::checkPlain($row->title);
    + *     $context['results'][] = $row->id . ' : ' . $row->title;
    ...
    - *     $context['message'] = SafeMarkup::checkPlain($row->title);
    + *     $context['message'] = $row->title;
    

    Twig will auto-escape these variables, except when it won't. It's definitely a lie about the second line now.

    OTOH, as @alexpott pointed out, the first example line in HEAD is a auto double-escape bug waiting to happen.

xjm’s picture

  1. +++ b/core/includes/update.inc
    @@ -215,7 +214,7 @@ function update_do_one($module, $number, $dependency_map, &$context) {
    -  $context['message'] = 'Updating ' . SafeMarkup::checkPlain($module) . ' module';
    +  $context['message'] = 'Updating ' . $module . ' module';
    

    Another example in HEAD of where we protect site builders from XSS exploits introduced by module authors by embedding XSS (or is it SSS, "same-site scripting"?) in their module names. :P But if this were actual user input, I think this is a case where an XSS bug would be introduced.

  2. +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
    @@ -206,8 +210,8 @@ public static function checkPlain($text) {
    -   *       printed as an HTML attribute value and therefore formatted with
    -   *       self::checkPlain() as part of that.
    +   *       printed as an HTML attribute value and therefore escaped as part of
    +   *       that.
    

    @todo for self find the code change relevant for this docs change.

  3. +++ b/core/lib/Drupal/Component/Utility/Xss.php
    @@ -107,10 +107,9 @@ public static function filter($string, array $html_tags = NULL) {
    -   * Use only for fields where it is impractical to use the
    -   * whole filter system, but where some (mainly inline) mark-up
    -   * is desired (so \Drupal\Component\Utility\SafeMarkup::checkPlain() is
    -   * not acceptable).
    +   * Use only for fields where it is impractical to use the whole filter system,
    +   * but where some (mainly inline) mark-up is desired (so
    +   * \Drupal\Component\Utility\Html::escape() is not acceptable).
    

    NIH, but this hunk of documentation is already a little misleading in HEAD. Xss::filterAdmin() has way more usecases than non-input-filter-filtered fields.

xjm’s picture

+++ b/core/lib/Drupal/Core/Controller/TitleResolverInterface.php
@@ -17,18 +17,20 @@
    *
-   * The returned title string must be safe to output in HTML. For example, an
-   * implementation should call \Drupal\Component\Utility\SafeMarkup::checkPlain()
-   * or \Drupal\Component\Utility\Xss::filterAdmin() on the string, or use
-   * appropriate placeholders to sanitize dynamic content inside a localized
-   * string before returning it. The title may contain HTML such as EM tags.
+   * If the returned title can contain HTML that should not be escaped it should
+   * return a render array, for example:
+   * @code
+   * ['#markup' => 'title', '#allowed_tags' => ['em']]
+   * @endcode
+   * If the method returns a string and it is not marked safe then it will be
+   * auto-escaped.
...
-   * @return string|null
+   * @return array|string|null

Note to self: @alexpott says that the support for render arrays here was introduced by #2549791: Remove SafeMarkup::xssFilter() and provide ability to define #markup escaping strategy and what tags to filter and there is discussion on that issue. The docs for the interface just didn't get updated. ( title_resolver service used for _title_callback on routes etc.) Edit: the discussion apparently actually was in IRC; on that issue there is just comment #22.

xjm’s picture

+++ b/core/lib/Drupal/Core/Diff/DiffFormatter.php
@@ -172,7 +172,7 @@ protected function emptyLine() {
   protected function _added($lines) {
     foreach ($lines as $line) {
-      $this->rows[] = array_merge($this->emptyLine(), $this->addedLine(SafeMarkup::checkPlain($line)));
+      $this->rows[] = array_merge($this->emptyLine(), $this->addedLine(Html::escape($line)));

@@ -181,7 +181,7 @@ protected function _added($lines) {
   protected function _deleted($lines) {
     foreach ($lines as $line) {
-      $this->rows[] = array_merge($this->deletedLine(SafeMarkup::checkPlain($line)), $this->emptyLine());
+      $this->rows[] = array_merge($this->deletedLine(Html::escape($line)), $this->emptyLine());

@@ -190,7 +190,7 @@ protected function _deleted($lines) {
   protected function _context($lines) {
     foreach ($lines as $line) {
-      $this->rows[] = array_merge($this->contextLine(SafeMarkup::checkPlain($line)), $this->contextLine(SafeMarkup::checkPlain($line)));
+      $this->rows[] = array_merge($this->contextLine(Html::escape($line)), $this->contextLine(Html::escape($line)));

It might be worth testing manually to ensure no double-escaping bug is introduced here.

xjm’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/EntityListBuilder.php
    @@ -112,10 +111,10 @@ protected function getEntityIds() {
        * @return string
    -   *   The escaped entity label.
    +   *   The entity label.
        */
       protected function getLabel(EntityInterface $entity) {
    -    return SafeMarkup::checkPlain($entity->label());
    +    return $entity->label();
    

    I confirmed this protected method is only used in core by the base implementation's render() method and that only the block implementation overrides it, and that in both cases, the label will get escaped anyway (so the checkPlain() is redundant). However, it is possible that a contrib or custom list builder implementation that used the label in a different way could have an XSS vulnerability following this patch, because this patch does change the documented behavior of the method. It is a correct change (see discussion on #2503963: XSS in Quick Edit: entity title is not safely encoded), but it might merit its own change record for this reason.

  2. +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php
    @@ -235,7 +234,7 @@ public function getReferenceableEntities($match = NULL, $match_operator = 'CONTA
    -      $options[$bundle][$entity_id] = SafeMarkup::checkPlain($entity->label());
    +      $options[$bundle][$entity_id] = $entity->label();
    

    This is similarly changing the documented behavior of SelectionInterface::getReferenceableEntities(), which says (emphasis added):

    A nested array of entities, the first level is keyed by the entity bundle, which contains an array of entity labels (safe HTML), keyed by the entity ID.

    This is also a public method and, being Entity Reference and therefore JavaScripty, something we should check very carefully for potential XSS.

xjm’s picture

xjm’s picture

One suggestion I had for issue management here was to split off all the things that are straight conversions of checkPlain() to Html::escape() because there is no risk of introducing XSS vulnerabilities whatsoever, and the only thing they need to be checked for is the introduction of double- (edit:single- or double-) escaping bugs that lack test coverage. That could land first and make the rest more manageable.

joelpittet’s picture

Divide and conquer! +1 to #121

alexpott’s picture

Wim Leers’s picture

#2557519: Remove many usages SafeMarkup::checkPlain() and replace with Html::escape() landed. So, soon this patch will be much easier to review :)

alexpott’s picture

Status: Needs work » Needs review
FileSize
197.44 KB

Here's a reroll. I've come round to think that #2555931: Add #plain_text to escape text in render arrays needs to go in first - or at least the part of it that makes the escaping strategy always apply. This is because this is how SafeMarkup::checkPlain() works - it does not check if something is safe before escaping.

This patch also includes fixes for #115, #116.1, #118 (with a test).

Next, I think we could look at removing SafeMarkup::checkPlain from template preprocess functions because there it is quite clear that Twig will auto-escape.

alexpott’s picture

183 usages to go! :)

alexpott’s picture

Status: Needs review » Needs work

The last submitted patch, 126: 2545972.126.patch, failed testing.

alexpott’s picture

Status: Needs review » Needs work

The last submitted patch, 130: 2545972.130.patch, failed testing.

alexpott’s picture

lauriii’s picture

I didn't see any discussion about this in the issue so how are we going to support the PHP templating engine if we start trusting on the Twig autoescape?

star-szr’s picture

@lauriii I think the short answer is that ship sailed long ago.

stefan.r’s picture

alexpott’s picture

@stefan.r no it's not - it was when this issue added Html::escape() but it no longer does that.

star-szr’s picture

Status: Needs review » Needs work

The last submitted patch, 137: remove_all_usages-2545972-136.patch, failed testing.

star-szr’s picture

After chatting with @alexpott here's a new issue and patch up for review, nice and straightforward: #2559605: Convert SafeMarkup::checkPlain() in render arrays to #plain_text

stefan.r’s picture

Given that last patch will fatal error now that we don't use the Markup class anymore, here's a combined patch with #2559605: Convert SafeMarkup::checkPlain() in render arrays to #plain_text that does a simple search and replace on all other instances of #markup + Markup::SAFE_STRATEGY_ESCAPE with #plain_text. Which we can't just do as some things will be double escaped, so this will give a few test failures.

Maybe let's keep future patches in this issue synchronized with #2559605: Convert SafeMarkup::checkPlain() in render arrays to #plain_text and do a reverse patch as soon as it goes in?

stefan.r’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 140: 2545972-140-fail.patch, failed testing.

alexpott’s picture

stefan.r’s picture

Status: Needs work » Needs review
FileSize
174.34 KB
4.1 KB

reverting those

alexpott’s picture

We've got at least 4 issues to go in before we need to return to this one...

Let's get all of those in before returning here to see what is left...

stefan.r’s picture

Status: Needs review » Postponed
stefan.r’s picture

Status: Postponed » Needs work

The last submitted patch, 144: 2545972-144.patch, failed testing.

stefan.r’s picture

Status: Needs work » Postponed

per #145

stefan.r’s picture

Just blocked on #2549393: Remove SafeMarkup::escape() and rely more on Twig autoescaping anymore, the other 3 issues have been commited now.

alexpott’s picture

Status: Postponed » Needs review
FileSize
138 KB

This is where we're currently at...

I think the next two things to split up are the render array changes and the radio/checkbox auto-escaping.

Status: Needs review » Needs work

The last submitted patch, 151: 2545972.151.patch, failed testing.

alexpott’s picture

Created #2560751: Remove all usages SafeMarkup::checkPlain() from Views titleQuery() and #2560641: Remove all usages SafeMarkup::checkPlain() from render arrays... just need to create one for the radio/checkbox auto-escaping. And then I'll postpone again.

alexpott’s picture

alexpott’s picture

lauriii’s picture

I have updated all the CRs mentioned in #94 except https://www.drupal.org/node/2153775 and https://www.drupal.org/node/1762604 where I wasn't sure what should be done.

mpdonadio’s picture

#156 thanks for updating the CRs. I'm not totally sure about some of them, though. In [#2372691], the change was essentially

from

$this->assertRaw(String::checkPlain($string1));
$this->assertNoRaw(String::checkPlain($string2));

to

$this->assertRaw(Html::escape($string1));
$this->assertNoRaw(Html::escape($string2));

In some of these, given the newness of Html::escape do we maybe want to keep both example of what not to use? In other words, people reading the CR may see Html::escape and not think the same thing applies to an equivalent usage of String::checkPlain? Especially since we are just deprecating SafeMarkup::checkPlain and not removing it altogether.

I updated that CR as an example.

xjm’s picture

I don't understand why we would keep any references to checkPlain() in any change record. If someone doesn't know what Html::escape() is, they will look it up.

Oh, I read https://www.drupal.org/node/2372691 and I think I get it. If the change record is specifically about updating D7 code, then yes, having the D7 "before" snippet makes sense.

joelpittet’s picture

Just regarding the issue summary about nl2br, l did a fix here that's pretty handy for that case.
#2554755: BasicStringFormatter's nl2br() forces escaping *and* filtering — improve this

alexpott’s picture

Status: Needs work » Needs review
FileSize
47.21 KB

Progress report in patch form. 48K not bad. Added a few @todo test comments where I think we should be adding a test. I guess each could be split into a separate issue :(

Status: Needs review » Needs work

The last submitted patch, 160: 2545972-2-160.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
530 bytes
47.21 KB

oops

Status: Needs review » Needs work

The last submitted patch, 162: 2545972-2-162.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
681 bytes
9.7 KB
alexpott’s picture

The last submitted patch, 164: 2568517-164.patch, failed testing.

dawehner’s picture

  1. +++ b/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php
    @@ -938,7 +937,7 @@ public function summaryName($data) {
         }
    -    return SafeMarkup::checkPlain($value);
    +    return $value;
       }
    
    @@ -957,7 +956,8 @@ public function query($group_by = FALSE) {
       function title() {
    -    return SafeMarkup::checkPlain($this->argument);
    +    // @todo test
    +    return $this->argument;
    

    Added: #2568965: Write a test view with extensive XSS test of all the things in views_ui

  2. +++ b/core/modules/views/views.module
    @@ -67,8 +67,8 @@ function views_views_pre_render($view) {
    -          'view_args' => SafeMarkup::checkPlain(implode('/', $view->args)),
    -          'view_path' => SafeMarkup::checkPlain(Url::fromRoute('<current>')->toString()),
    +          'view_args' => Html::escape(implode('/', $view->args)),
    +          'view_path' => Html::escape(Url::fromRoute('<current>')->toString()),
    

    I'm actually confused why we care about that. this again is JS, well we certainly don't need SafeMarkup here

  3. +++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
    @@ -740,7 +740,7 @@ public function testRenderCacheProperties(array $expected_results) {
    -      '#custom_property' => SafeMarkup::checkPlain('custom_value'),
    +      '#custom_property' => SafeString::create('custom_value'),
    

    lol

Status: Needs review » Needs work

The last submitted patch, 165: 2545972-2-165.patch, failed testing.

The last submitted patch, 160: 2545972-2-160.patch, failed testing.

The last submitted patch, 162: 2545972-2-162.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
601 bytes
48.18 KB

Fix the test.

The last submitted patch, 164: 2568517-164.patch, failed testing.

The last submitted patch, 165: 2545972-2-165.patch, failed testing.

alexpott’s picture

Resolving a lot of the @todos in the patch by adding tests. What's left over is some views plugins which don't have tests and usernames which are special because I need to create a test module to add markup to the username. The funky thing here is what is supposed to happen? If a module is permitting markup in a username display then I guess it will be marking it as safe too since otherwise what is the point. By default no markup is allowed in a username.

alexpott’s picture

Added test coverage for the rest of the @todo's in the patch with two exceptions:

Also the changes to not use SafeMarkup::format in the AssertContentTrait are totally related without them the safeness of a string was getting in the way of seeing the proper message and that is just wrong.

dawehner’s picture

  1. +++ b/core/includes/form.inc
    @@ -601,8 +604,11 @@ function template_preprocess_form_element_label(&$variables) {
    + *   // the user via the item list theme then it will be auto-escaped.
    + *   $context['results'][] = $node->id() . ' : ' . $node->label();
    + *   // This is used in JavaScript and should be escaped.
    + *   $context['message'] = Html::escape($node->label());
    

    This is honestly super confusing. the JS should do the escaping, otherwise people will escape either both or none. Nobody will remember that you need to escape just one of them.

  2. +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionLoggingSubscriber.php
    @@ -45,7 +45,7 @@ public function __construct(LoggerChannelFactoryInterface $logger) {
         $request = $event->getRequest();
    -    $this->logger->get('access denied')->warning(SafeMarkup::checkPlain($request->getRequestUri()));
    +    $this->logger->get('access denied')->warning('%uri', ['%uri' => $request->getRequestUri()]);
    
    @@ -56,7 +56,7 @@ public function on403(GetResponseForExceptionEvent $event) {
         $request = $event->getRequest();
    -    $this->logger->get('page not found')->warning(SafeMarkup::checkPlain($request->getRequestUri()));
    +    $this->logger->get('page not found')->warning('%uri', ['%uri' => $request->getRequestUri()]);
       }
    

    Won't that break people who try to group by most 404 pages?

  3. +++ b/core/modules/block/src/Controller/BlockController.php
    @@ -55,7 +56,7 @@ public static function create(ContainerInterface $container) {
         $page = [
    -      '#title' => $this->themeHandler->getName($theme),
    +      '#title' => Html::escape($this->themeHandler->getName($theme)),
    

    Do we really have to take care of #title ? I would have expected it to be autoescaped in page.html.twig

  4. +++ b/core/modules/block/src/Controller/CategoryAutocompleteController.php
    @@ -59,7 +59,7 @@ public function autocomplete(Request $request) {
    -        $matches[] = array('value' => $category, 'label' => SafeMarkup::checkPlain($category));
    +        $matches[] = array('value' => $category, 'label' => Html::escape($category));
    

    Too bad we don't escape in JS

  5. +++ b/core/modules/comment/src/Plugin/views/argument/UserUid.php
    @@ -65,7 +65,8 @@ function title() {
    -    return SafeMarkup::checkPlain($title);
    +    // @todo test
    +    return $title;
    

    We can remove SafeMarkup here as well

  6. +++ b/core/modules/filter/src/Tests/FilterUnitTest.php
    @@ -480,7 +480,7 @@ function testNoFollowFilter() {
    -   * \Drupal\Component\Utility\SafeMarkup::checkPlain() is not tested here.
    +   * \Drupal\Component\Utility\Html::escape() is not tested here.
        */
       function testHtmlEscapeFilter() {
    

    Yeah this is BS, ... I mean its implicit tested here. What about dropping that line of documentation?

  7. +++ b/core/modules/node/src/Tests/PagePreviewTest.php
    @@ -8,6 +8,7 @@
     use Drupal\comment\Tests\CommentTestTrait;
    +use Drupal\Component\Utility\Html;
     use Drupal\Core\Field\FieldStorageDefinitionInterface;
    

    unused

  8. +++ b/core/modules/taxonomy/src/Plugin/views/field/TaxonomyIndexTid.php
    @@ -153,7 +152,7 @@ public function preRender(&$values) {
               $this->items[$node_nid][$tid]['vocabulary_vid'] = $term->getVocabularyId();
    -          $this->items[$node_nid][$tid]['vocabulary'] = SafeMarkup::checkPlain($vocabularies[$term->getVocabularyId()]->label());
    +          $this->items[$node_nid][$tid]['vocabulary'] = $vocabularies[$term->getVocabularyId()]->label();
     
               if (!empty($this->options['link_to_taxonomy'])) {
    

    Yeah for twig!

  9. +++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
    @@ -359,7 +358,7 @@ public function adminSummary() {
           foreach ($terms as $term) {
    -        $this->valueOptions[$term->id()] = SafeMarkup::checkPlain(\Drupal::entityManager()->getTranslationFromContext($term)->label());
    +        $this->valueOptions[$term->id()] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
    

    Are you sure you don't introduce an XSS here? We have a list of checkboxes in InOperator.php, that leverages $this->valueOptions, so don't we need escaping?

  10. +++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyDefaultArgumentTest.php
    @@ -59,4 +59,13 @@ public function testTermPath() {
    +    $this->drupalGet('/taxonomy_default_argument_test/'. $this->term1->id());
    

    So we really support a starting slash in URLs in tests? I thought this would not be the case.

  11. +++ b/core/modules/tracker/src/Controller/TrackerUserTab.php
    @@ -28,6 +28,7 @@ public function getContent(UserInterface $user) {
    -    return SafeMarkup::checkPlain($user->getUsername());
    

    We can remove SafeMarkup here

  12. +++ b/core/modules/views/src/Plugin/views/area/Result.php
    @@ -87,7 +87,7 @@ public function render($empty = FALSE) {
    +    $label = Html::escape($this->view->storage->label());
    

    OH, its super tricky to not accidentally think its not needed.

  13. +++ b/core/modules/views/src/Plugin/views/field/PrerenderList.php
    @@ -78,17 +78,24 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    -        return implode($this->sanitizeValue($this->options['separator'], 'xss_admin'), $items);
    +        $render = [
    +          '#type' => 'inline_template',
    +          '#template' => '{{ items|safe_join(separator) }}',
    +          '#context' => [
    +            'items' => $items,
    +            'separator' => $this->sanitizeValue($this->options['separator'], 'xss_admin')
    +          ]
    +        ];
           }
           else {
    -        $item_list = array(
    +        $render = array(
               '#theme' => 'item_list',
               '#items' => $items,
    

    Why do we not always uyse item_list, once with class comment and once without it?

alexpott’s picture

  1. I agree but this is not actually change by this patch! It is just fixing the document to be correct. The fact that Twig auto escapes and JS does not is not the fault of this patch. I agree with @dawehner that the escaping should be handled client side - making it impossible to forgot but then we have to learn how to communicate the a string is safe to the JS layer.

    If this will remain contentious how about we punt this hunk to the follow that will actually remove SafeMarkup::checkPlain()?

  2. The use of % is wrong but @ is correct admin/reports/page-not-found works just fine. And syslog does a strtr so I think this is fine.
  3. Nice spot - this would have been a (very rare) double escaping bug. Added a test.
  4. Yep it is
  5. Fixed
  6. Indeed it is - removed
  7. Fixed
  8. Yay!
  9. Nope - this does not going into an options form it is the admin summary that appears on the main view page.
  10. Yes it works but it looks very weird - fixed
  11. Fixed
  12. Yes I've stared long and hard at that before.
  13. We don't always use item list because the inline list does not have a customisable separator. This is just how this works.

The last submitted patch, 177: 2545972-2-177.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 177: 2545972-2-177.patch, failed testing.

The last submitted patch, 177: 2545972-2-177.patch, failed testing.

The last submitted patch, 177: 2545972-2-177.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
82.51 KB
3.11 KB
  1. +++ b/core/includes/form.inc
    @@ -574,10 +574,13 @@ function template_preprocess_form_element_label(&$variables) {
    + * therefore it is responsibility the of the batch callbacks to handle
    

    Nit: s/the of the/of the/

  2. +++ b/core/includes/form.inc
    @@ -574,10 +574,13 @@ function template_preprocess_form_element_label(&$variables) {
    + * sanitization if required. If the 'result' key is being theme using item_list
    

    Parse error: "is being theme using"

    Oh, I think I see it: s/theme/themed/

  3. +++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyDefaultArgumentTest.php
    @@ -59,4 +59,13 @@ public function testTermPath() {
    +  }
     }
    

    Missing blank line.

  4. +++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermArgumentDepthTest.php
    @@ -0,0 +1,65 @@
    +    $this->terms[0] = $first;
    +
    +  }
    

    Extraneous blank line.

Fixed these nits.


+++ b/core/modules/system/tests/themes/test_theme/test_theme.info.yml
@@ -8,7 +8,7 @@
-name: 'Test theme'
+name: '<strong>Test theme</strong>'

This caused a different sorting, and therefore a test failure. Fixed this too.


Is there anything left that is blocking RTBC?

alexpott’s picture

For me the only thing that blocks RTBC is

+++ b/core/includes/form.inc
@@ -574,10 +574,13 @@ function template_preprocess_form_element_label(&$variables) {
  * Note: if the batch 'title', 'init_message', 'progress_message', or
  * 'error_message' could contain any user input, it is the responsibility of
  * the code calling batch_set() to sanitize them first with a function like
- * \Drupal\Component\Utility\SafeMarkup::checkPlain() or
+ * \Drupal\Component\Utility\Html::escape() or
  * \Drupal\Component\Utility\Xss::filter(). Furthermore, if the batch operation
- * returns any user input in the 'results' or 'message' keys of $context, it
- * must also sanitize them first.
+ * returns any user input in the 'message' key of $context, it must also
+ * sanitize it first. The 'result' key is not displayed to the user and
+ * therefore it is responsibility of the batch callbacks to handle sanitization
+ * if required. If the 'result' key is being themed using item_list the callback
+ * can rely on Twig auto-escape.
  *
  * Sample callback_batch_operation():
  * @code
@@ -601,8 +604,11 @@ function template_preprocess_form_element_label(&$variables) {

@@ -601,8 +604,11 @@ function template_preprocess_form_element_label(&$variables) {
  *
  *   $nodes = entity_load_multiple_by_properties('node', array('uid' => $uid, 'type' => $type));
  *   $node = reset($nodes);
- *   $context['results'][] = $node->id() . ' : ' . SafeMarkup::checkPlain($node->label());
- *   $context['message'] = SafeMarkup::checkPlain($node->label());
+ *   // This can be used by the batch finished callback. If this is displayed to
+ *   // the user via the item list theme then it will be auto-escaped.
+ *   $context['results'][] = $node->id() . ' : ' . $node->label();
+ *   // This is used in JavaScript and should be escaped.
+ *   $context['message'] = Html::escape($node->label());
  * }
  *
  * // A more advanced example is a multi-step operation that loads all rows,
@@ -621,10 +627,13 @@ function template_preprocess_form_element_label(&$variables) {

@@ -621,10 +627,13 @@ function template_preprocess_form_element_label(&$variables) {
  *     ->range(0, $limit)
  *     ->execute();
  *   foreach ($result as $row) {
- *     $context['results'][] = $row->id . ' : ' . SafeMarkup::checkPlain($row->title);
+ *     // This can be used by the batch finished callback. If this is displayed to
+ *     // the user via the item list theme then it will be auto-escaped.
+ *     $context['results'][] = $row->id . ' : ' . $row->title;
  *     $context['sandbox']['progress']++;
  *     $context['sandbox']['current_id'] = $row->id;
- *     $context['message'] = SafeMarkup::checkPlain($row->title);
+ *     // This is used in JavaScript and should be escaped.
+ *     $context['message'] = Html::escape($row->title);
  *   }
  *   if ($context['sandbox']['progress'] != $context['sandbox']['max']) {

This hunk is tricky. The dissonance between late escaping in Twig and early escaping in JavaScript is shoved right in the users face here. Given that I think best thing to will be to discuss this in the issue that actually removes SafeMarkup::checkPlain() since that is where this will be completely real.

Also as this is not removing the method the change record work can occur with the next issue.

alexpott’s picture

Title: Remove all usages SafeMarkup::checkPlain() and rely more on Twig autoescaping » Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping
dawehner’s picture

So what is the problem in actually escaping in javascript? BatchAPI is a limited subset of the JS, which we could easily patch, can't we?

alexpott’s picture

re #185 let's delay discussion of the batch API to another issue and proceed here. For example #2569699: Remove SafeMarkup::checkPlain() for Drupal 9.0.x.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Agreed

Wim Leers’s picture

+1

catch’s picture

Status: Reviewed & tested by the community » Fixed
  1. +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionLoggingSubscriber.php
    @@ -45,7 +44,7 @@ public function __construct(LoggerChannelFactoryInterface $logger) {
       }
    

    I had to ask @alexpott about this one.

    Currently we escape on the way in - not great for syslog.

    By making it a placeholder, we escape on render in dblog - which is fine.

    If we didn't escape at all, it would get XSS filtered but it should be HTML escaped anyway.

    We should add test coverage for this, although for me I'm fine with that in a follow-up.

  2. +++ b/core/lib/Drupal/Core/Utility/Token.php
    @@ -285,8 +285,8 @@ public function scan($text) {
    +   *     \Drupal\Component\Utility\Html::escape() or other appropriate scrubbing
    

    'appropriate scrubbing functions' is.... but also #2567257: hook_tokens() $sanitize option incompatible with Html sanitisation requirements is open to fix the tokens confusion.

  3. +++ b/core/modules/block/src/Controller/BlockController.php
    @@ -55,7 +56,7 @@ public static function create(ContainerInterface $container) {
    +      '#title' => Html::escape($this->themeHandler->getName($theme)),
    

    Slightly perturbed this is needed for #title but I think that was already said by dawehner earlier.

This gets us a long way forward, anything remaining we can do on the removal issue as discussed above. Committed/pushed to 8.0.x, thanks!

  • catch committed 8691e08 on 8.0.x
    Issue #2545972 by alexpott, joelpittet, ericjenkins, stefan.r, Wim Leers...
dawehner’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.