Follow-up to #2545972: Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping

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

In #2545972: Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping we remove all usages in core. 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.

We should deprecate SafeMarkup::checkPlain() and decide if we want to remove it.

Remaining tasks

  1. Improve the change record discussion of checkPlain() replacements (see #32).
  2. Review all change records that contain references to check_plain and checkPlain and ensure they reflect current best practice. List the specific change records below once it has been confirmed they are up to date.

User interface changes

None

API changes

Deprecate SafeMarkup::checkPlain()

Data model changes

None

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Task because this is to decide whether we want to deprecate
Issue priority Major because SafeMarkup::checkPlain() is hard to use correctly and we should be using auto escape
Disruption Disruptive for core/contributed and custom modules/themes because it will require a BC break as SafeMarkup::checkPlain() no longer exists. However, as shown by the patch most cases will be fixed by just removing it. I think the disruption is worth it for a less complex and entangled SafeMarkup class.

Comments

alexpott created an issue. See original summary.

alexpott’s picture

Issue summary: View changes
mpdonadio’s picture

The biggest advantage to removing it is that it will force people to use the new methods. It it remains, I forsee a lot of SA-CONTRIB going out because contrib maintainers will just convert check_plain() usages into SafeMarkup::checkPlain(). Given all of the XSS SA that have gone out this year, I think this is a legitimate concern. I also think that we have enough examples to pull from from the current purge to fill out https://www.drupal.org/node/28984 nicely.

wim leers’s picture

#3: Nicely put :)

xjm’s picture

Discussed with @effulgentsia, @alexpott, @catch, and @webchick. We agreed to deprecate checkPlain() and that it is a critical part of minimizing SafeMarkup use. We will initially deprecate it for removal before 8.0.0.

Removing checkPlain() entirely or removing its core usages is not critical, but we can continue that work up to RC.

alexpott’s picture

Title: Deprecate SafeMarkup::checkPlain() and decide if we want to remove it in 8.0.x » Deprecate SafeMarkup::checkPlain() and SafeMark::escape() for Drupal 8.0.x
catch’s picture

Just to clarify what I think the plan is:

Deprecate for 8.0.0 now, without removing all usages yet, to send a clear message via change notices etc. that contrib should remove their own usages asap.

If we're able to remove all core usages before a release candidate, try to remove it.

If we're not able to remove all core usages before a release candidate, deprecate for 9.x instead and/or mark @internal and possibly try to make it so neither do anything at all with the safe list.

catch’s picture

Tagging this as beta target since it's a deliberate API change that we want to expose to contrib as soon as possible.

stefan.r’s picture

Issue tags: +@deprecated

Could we do something like this, on both methods:

@deprecated Will be removed before Drupal 8.0.0. If possible, rely on Twig's auto-escaping feature, or use the '#plain_text' key when constructing a render array that contains plain text in order to use the renderer's auto-escaping feature. If neither of these are possible, Html::escape() can be used in places where explicit escaping is needed.

...whenever (and if) #2555931: Add #plain_text to escape text in render arrays goes in?

cilefen’s picture

Issue tags: +Novice

A novice critical? A novice can create the patch.

plach’s picture

Title: Deprecate SafeMarkup::checkPlain() and SafeMark::escape() for Drupal 8.0.x » Deprecate SafeMarkup::checkPlain() and SafeMarkup::escape() for Drupal 8.0.x
plach’s picture

Status: Active » Needs review
StatusFileSize
new1.59 KB

Something like this?

catch’s picture

Just committed #2555931: Add #plain_text to escape text in render arrays which was soft-blocking this.

alexpott’s picture

Title: Deprecate SafeMarkup::checkPlain() and SafeMarkup::escape() for Drupal 8.0.x » Deprecate SafeMarkup::checkPlain() for Drupal 8.0.x
StatusFileSize
new860 bytes

Actually I think looking at #2549393: Remove SafeMarkup::escape() and rely more on Twig autoescaping we only need to deal with SafeMarkup::checkPlain here...

cilefen’s picture

StatusFileSize
new1000 bytes
new848 bytes

I think this wording is a little more direct. To me, "possibly" used twice sounds like an invitation to do whatever I want.

dawehner’s picture

+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -169,6 +169,13 @@ public static function getAll() {
+   *   auto-escaping feature, or, use the '#plain_text' key when constructing a

What about pointing to the corresponding docs (https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21...) ? I think you can use @link for that, not sure about the syntax though

stefan.r’s picture

StatusFileSize
new873 bytes

Adding in that link and removing a superfluous comma

stefan.r’s picture

StatusFileSize
new1.07 KB
lauriii’s picture

+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -169,6 +169,13 @@ public static function getAll() {
+   * @deprecated Will be removed before Drupal 8.0.0. Rely on Twig's
+   *   auto-escaping feature, or use the @link theme_render #plain_text @endlink

IMO this comment should not be written only for the Twig because we still have to support PHP templating engine. At least we shouldn't make assumptions that everyone is using Twig.

lauriii’s picture

So it doesn't actually leave out users of other templating engines but instead the structure was a little hard to process for me. Anyway I think the structure is good as it is because now it very explicitly tells in the first sentence that if you use Twig, there shouldn't be need to care about escaping manually.

cilefen’s picture

#20 - The confusing sentence structure was the intention of the "superfluous" comma.

dawehner’s picture

I agree that , or, was confusing to read, at least in english.

Does this issue needs its own dedicated change record, or should we prepare one big one for all the issues?

alexpott’s picture

I think we should have one CR that reflects the 8.x changes wrt to SafeMarkup (https://www.drupal.org/node/2549395) and one change record that details Twig auto-escape (https://www.drupal.org/node/2296163). If we must have other CRs they should be very short and just point to one of these two.

stefan.r’s picture

Updated the CR at https://www.drupal.org/node/2549395 with a bullet point about checkPlain

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

I think this ready. We've got the CR and consensus. My contribution was a removal.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.0.x, thanks!

  • catch committed 711872e on 8.0.x
    Issue #2555473 by stefan.r, cilefen, alexpott, plach: Deprecate...
xjm’s picture

In #2545972-95: Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping I listed out a bunch of CRs that also needed to be updated for this change (others are/were mentioned in #2545972-84: Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping). It might be worth reopening this for that, because it's not just a simple replacement; we want to dissuade people from escaping inappropriately.

xjm’s picture

Status: Fixed » Needs work
Issue tags: +Needs change record updates

Also, I think that the section on checkPlain() in https://www.drupal.org/node/2549395 needs more work to better clarify when checkPlain() should be replaced and with what. For example, it says "SafeMarkup::checkPlain() - If the string will end up in a Twig template, rely on Twig's auto-escaping feature." This is pretty vague and confusing -- one way or another, most things will "end up" in Twig templates. If modules remove their escaping incorrectly, it will still open XSS vulnerabilities. It might be better to say that Html::escape() is the most direct replacement for SafeMarkup::checkPlain() so people who don't pay attention to the details start there, and then to go into more detail about other strategies, with more examples. We should also emphasize that the developer should test carefully when converting code to rely on Twig's autoescape.

Also, I think it's important to emphasize that non-HTML output must still be sanitized, either by using Html::escape() when (e.g.) constructing the response, or by escaping in (e.g.) JavaScript à la #2503963: XSS in Quick Edit: entity title is not safely encoded.

Reopening for that as well as #28. Thanks everyone!

xjm’s picture

xjm’s picture

Title: Deprecate SafeMarkup::checkPlain() for Drupal 8.0.x » [change record updates] Deprecate SafeMarkup::checkPlain() for Drupal 8.0.x

Sorry, forgot to update the title. Also, the doc in #30 now has public comment access, but the post should go out in a bit anyway. :)

xjm’s picture

Alright, @alexpott, @effulgentsia, and I reworked the main change record for the method removals in SafeMarkup methods are removed. There are still a couple outstanding things that I think should be improved (copying and expanding notes from our google docs draft):

  1. If the string is used in a Twig template, rely on Twig's auto-escaping feature. Simply remove the call to SafeMarkup::checkPlain(). For example, most SafeMarkup::checkPlain() calls in template preprocess functions can be removed. Another common case is the construction of a render array that uses a Twig template. For example, data that is being themed using an item list or table does not need to be escaped as Twig will do this for you. If text that has already been escaped using Html::escape() ends up in a Twig template variable, it will be double-escaped.

    This is clearer now, but as a reader of this I still would not entirely understand "is used in a template". First of all, there are ways to use strings unsafely in a template. Do we have a Twig resource we can reference about how output ends up in templates and how it is used safely? Secondly, there are many cases where a developer is creating output that will be used in an existing template without the developer knowing anything about it. (Maybe we need a chart somewhere indicating which strings will be escaped, which strings will be filtered, and which will not be sanitized. That doesn't need to block this though.)

  2. For example, most SafeMarkup::checkPlain() calls in template preprocess functions can be removed

    We should clarify when they can't be removed. For example, if the string is XSS-filtered, this will be more permissive than escaping and bypass it because the resulting string will be safe.

  3. Another common case is the construction of a render array that uses a Twig template. For example, data that is being themed using an item list or table does not need to be escaped as Twig will do this for you.

    This example is helpful. A code snippet might make it more so. However, some render array elements, like #prefix, #suffix, #value, and #markup are not escaped (only admin filtered) so this sentence is somewhat too vague.

xjm’s picture

Issue summary: View changes

I updated some of the references to check_plain or checkPlain yesterday, and it looks like someone worked on them today as well (thanks!). Some remain; adding links to search to the summary as well as the remaining task for the main CR.

xjm’s picture

Issue summary: View changes
chx’s picture

SafeMarkup::checkPlain() is dangerous ... decide if we want to remove it.

Here, I edited the summary for you to help with the decision.

alexpott’s picture

obviously https://www.drupal.org/node/2549395 and https://www.drupal.org/node/2296163 are good

Going through the rest of the CRs from point 2 of the issue summary.

I think we might be done.

xjm’s picture

Title: [change record updates] Deprecate SafeMarkup::checkPlain() for Drupal 8.0.x » Deprecate SafeMarkup::checkPlain() for Drupal 8.0.x
Status: Needs work » Fixed
Issue tags: -Needs change record updates

#37 looks good to me. Thanks @alexpott.

#32 has not been addressed entirely, but I don't think that's critical at this point, so moving that to the scope of #2494297: [no patch] Consolidate change records relating to safe markup and filtering/escaping to ensure cross references exist.

mustanggb’s picture

It's looking like #2545972: Remove all code usages SafeMarkup::checkPlain() and rely more on Twig autoescaping might land before RC, so shouldn't this issue be demoted from critical and postponed on that one, in-case removal rather than deprecation is possible?

catch’s picture

We need to deprecate before we remove, and ideally give as much notice as possible so people can update.

Also it was committed already.

Status: Fixed » Closed (fixed)

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