Problem/Motivation

The current Twig Sandbox Policy has several drawbacks (partly affecting security):

  • The whitelists for allowed method names and prefixes are hardcoded, overridable only in settings.php
  • This whitelist has insufficient granularity, i.e. allowing one method or prefix in one class allows that method or prefix in all classes

Proposed resolution

Modernize Twig Sandbox Policy to leverage attributes.
Non-Feature: This especially means, you can NOT allow methods that you do not control (no spooky action on a distance ;-). If you want to do that, write a wrapper.

Consider deprecating the old hard-coded policy, and possibly care for properties (which are essentially methods in php 8.5!) in a followup.

Remaining tasks

Code, review, commit.

Original Report

#2513266: Twig templates can call delete() on entities and other objects added the ability to whitelist what object methods were allowed to be called from a Twig template. These whitelists can be customized in a site's settings.php file by adding the appropriate $settings[' ... '] variable. This requires modules that need access to new method names to include extra documentation explaining how this is done and could lead to less secure code if site builders accidentally replace the default values instead of adding to them. See the original change record for more details.

Allowing modules and theme's to implement a service to handle white- or blacklisting a given method call would solve this. Similar to the node access ssytem, a service could respond ALLOW/DENY/NEUTRAL, allowing core to maintain it's current list while a module could add additional method names or have even greater restrictions in situations where Twig templates may come from untrusted sources (eg: user generated).

User interface changes

None.

Introduced terminology

None.

API changes

None.

Data model changes

None.

Release notes snippet

See CR draft.

Issue fork drupal-2595805

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mikeker created an issue. See original summary.

mikeker’s picture

Another, more disruptive, option is to use ViewModels so that a read-only copy of any object is passed to the Twig template. That would eliminate the need for a Twig Sandbox policy.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

fergusong’s picture

I agree that having custom modules specific configuration information
in settings.php is undesirable. The workaround I use is
to define a 'marker interface' (named TwigAccess) and
to allow that class in the settings.php. A 'marker interface'
is an PHP interface that does not specify any methods. Classes I intend for
twig access implement TwigAccess and be allowed access with
no further ado.

Two issues come to mind.
1. My app has a base module where I can define the shared interface
once and for all. Just the one class is added to settings.php when
the base module is installed.
Many apps won't have such a base module. Perhaps it would make sense to
add the marker interface to core?

2. Some people dislike marker interfaces on principle.
It does feel a little dodgey; but better than relying on settings.php.

Anyway, the TwigAccess interface approach solves my problem, might be of use to others,
and might point towards a resolution to this issue.

I'd be glad to post the little bits of code if this is of interest and unclear.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Status: Active » Reviewed & tested by the community

There has only been two comments here in 10 years. Is there anything to do here?

I am setting the status to Postponed (maintainer needs more info). If we don't receive additional information to help with the issue, it may be closed after three months.

Thanks!

quietone’s picture

Status: Reviewed & tested by the community » Postponed (maintainer needs more info)
rgpublic’s picture

Almost 10 years ago since this ticket was created. I'd like to bringt it back to life, because even after all this time, I think the request or problem stated is still very valid: Currently you can only extend the allowed methods and classes of Twig via $settings. A use can do this, but a module author can't do this. They could only add it to the documentation of their module: "Please, if you install this module you need to add this line to your settings and if you installed this module you can remove this line again". This is very inconvenient and unflexible. Modules should be able to provide objects like from the built in "Attribute" class to pass to twig that can be used freely in Twig and where you can call any method on. Actually, I believe you can currently inherit your class from Attribute and it will work, but that's of course a very ugly hack.

At the moment, there are some method prefixes that will always work, but considering the discussion in #3239105 this will get hardened in the future.

Now, there are many different options to solve this:

1) Provide an interface in core (TwigSafeInterface) that is included in core and has no methods but is by default considered safe. Any classes could implement this interface and be safe. Disadvantage here is that you can't just mark individual methods as safe.

2) Turn the policy into a service so it can be decorated: At least better than the current situation, and that's what's been requested for here in this issue, but of course a bit complicated for module authors with a significant amount of confusing boilerplate code.

3) IMHO the most modern method that is now available to us with modern PHP: Extend the default security policy so that it looks at PHP attributes like #[TwigSafe] on classes and methods. This would be the easiest and most flexible method to solve this IMHO.

rgpublic’s picture

Status: Postponed (maintainer needs more info) » Active
geek-merlin’s picture

Title: [Followup] Implement the Twig Sandbox Policy as a service collection » Leverage attributes in Twig Sandbox Policy
Issue summary: View changes
Issue tags: +Security improvements

Modernizing the IS.

geek-merlin’s picture

Issue summary: View changes

geek-merlin’s picture

Issue summary: View changes
Status: Active » Needs work
Issue tags: +Needs tests

Implementation is dead simple. NW for tests though.

geek-merlin’s picture

Status: Needs work » Needs review

Test is equally trivial.

geek-merlin’s picture

Issue summary: View changes

Added change record draft.

rgpublic’s picture

Looks great. Wonderful to see this finally moving forward after all this time. Thanks for the implementation! Just a few more thoughts on this: Do we also need to allow this on whole classes? That could eventually replace the somewhat arbitrary special case for HTMLAttribute... Or, do we force developers to use it on any individual method - could be safer, I don't know. And, looking at the issue summary, we still don't protect properties, right? I guess this would be only about writing to them. Is this actually possible in twig? set obj.test="xxx" isn't possible as far as I know. Might be wrong though.

geek-merlin’s picture

Issue summary: View changes
Issue tags: -Needs tests

@rgpublic:

> Thanks for the implementation!
Nice you like it. Maybe you want to review and RTBC.

> And, looking at the issue summary, we still don't protect properties, right?
> I guess this would be only about writing to them. Is this actually possible in twig?
You are right (yet!), so that point is minor and i removed it from the IS.

> Do we also need to allow this on whole classes? That could eventually replace the somewhat arbitrary special case for HTMLAttribute...
> Or, do we force developers to use it on any individual method - could be safer, I don't know.
I suppose the latter.

That said, i decided to focus on the first step with a huge quick-win, and postpone ALL other changes and bikeshedding discussions about deprecation paths to a followup.

rgpublic’s picture

Well, I don't know whether I'm entitled to do this, but if I take the "Reviewed and tested by the community" verbatim, I am part of the community :-) so....

The patch is really simple and looks good to me. I wanted to take my job a bit more seriously though, so I applied the patch to an actual project where I really had that problem this patch is meant to solve. Until now, I've subclassed my class from Attribute as a hack. I removed that and of course the sandbox exception appeared. Now, I've patched Drupal and added the attribute. Works! Removed the attribute again: Sandbox error. So this seems to all works as intended. Great! I can now confirm that this patch works in real life. I couldn't test the test (wow recursion, lol) because it didn't apply to my older Drupal version but it looks good to me as well.

One final thought, before I would set this to RTBC: I've noticed that the new folder (and namespace) is called "Attribute". Could it be a bit confusing that we already have a class that is called "Attribute"? Perhaps we should call the folder "Attributes"? Or perhaps even omit the subfolder altogether for just that single attribute so far? I don't know...

geek-merlin’s picture

Status: Needs review » Reviewed & tested by the community

Setting RTBC per #31

geek-merlin’s picture

#31.2:
Yes i wondered too whether the php attribute should live in a "PhpAttribute" dir, as we have a html attribute class called Attribute. I decided to go with the quasi-standard dir name. If maintainer or committer has a different view on this, it's a trivial change to make.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs followup

I really like this change - in fact I think it is fantastic and think we should consider using it in core as a follow-up.

However I think the documentation on the attribute needs work as we should provide context about when it should be used.

geek-merlin’s picture

> I really like this change - in fact I think it is fantastic and think we should consider using it in core as a follow-up.

Nice you like it!

geek-merlin’s picture

Self-review: This adds TwigAllowed method attribute. Legacy pattern-based whitelisting is added to that. Maybe add a TwigAllowedByAttributeOnly class attribute, to opt out of legacy behavior. This is needed to cleanly deprecate legacy.

geek-merlin’s picture

Status: Needs work » Reviewed & tested by the community

> However I think the documentation on the attribute needs work as we should provide context about when it should be used.

Yes right. If this is the future, it must be well documented and good UX.
Added verbose docs, and updated the CR.
Also thought a bit about possible deprecation paths, and added an all-or-nothing opt-in class attribute, plus tests.

Tests are green (spellchecking has cleanup hiccups).
Test-only tests fail like they should. So RTBC again.

geek-merlin’s picture

Added followups, postponed for now.

geek-merlin’s picture

Status: Reviewed & tested by the community » Needs review

Ups, of cource technically the changes need review.

rgpublic’s picture

I'm unsure whether the "TwigAllowedByAttributeOnly" is really such a great idea. We'd need to deprecate this attribute again in the future. Anyone using the new TwigAllowed attribute would/should be aware of the current whitelist. Currently whitelisted are e.g. methods starting with get / is etc. I think it would probably be just fine if the new TwigAllowed attribute is additive to the whitelist - just as proposed - and then one day we do hand out deprecation notices for every method call that is just allowed due to the whitelist but missing that attribute. In a further step, the whitelist would be set to empty and then subsequently removed. I think that deprecation path would be sufficient and wouldn't need that additional auxiliary attribute. Just my thoughts on this...

geek-merlin’s picture

Hmm, i see your point!

So we have:
- Verion 1 - only TwigAllowed
- Bad: In the first step, whitelisting for a class can be a mixture of (legacy) Magic and explicit Attributes
- Good: Only one attribute, only one deprecation step
- Verion 2 - TwigAllowed + Class attribute
- Good: The class attribute makes it crystal clear if legacy Magic OR explicit Attributes rule
- Bad: One more deprecation step

I don't have strong feelings.
@alexpott, do you have?

joelpittet’s picture

Echoing @rgpublic in #40. Thanks so much for picking this up again @geek-merlin and @rgpublic! Let’s go with version 1 on a single #[TwigAllowed] attribute and skip TwigAllowedByAttributeOnly (looks like you did already if I read the latest code correctly). Seems like a simpler approach with less clean-up later.

alexpott’s picture

I merged 11.x in because the branch was very out-of-date and that was causing pipeline fails for spell-checking.

alexpott’s picture

Status: Needs review » Needs work

I think adding TwigAllowedByAttributeOnly is unnecessary too - let's remove that complexity - it is still in the MR.

geek-merlin’s picture

Thank you all for your feedback, and for saving me from barking up the wrong tree ;-). Partial revert is coming.

geek-merlin’s picture

Status: Needs work » Needs review

Here we go: Initial 1-attribute approach, verbose phpdoc. CR updated too.
Tests are green, test-only red the way expected.

alexpott’s picture

Please someone get this to RTBC - I'd love to land this in 11.3.0

geek-merlin’s picture

Bump!

geek-merlin’s picture

@alexpott: I can't self-RTBC, but code-wise this this is back to #37 which was RTBC.

rgpublic’s picture

Status: Needs review » Reviewed & tested by the community

I tried the new version from #45 again on my project. It works. The code is also pretty simple, so I think I can safely set this to RTBC again. I'm also really looking forward to this. It's really a brilliant step forward for a saner, more modern and better extensible Drupal/Twig ecosystem IMHO. It just makes so much more sense. The current situation is also not really safe. If there was e.g. some method "function isleOfManDestroy()" the beautiful British isle could be destroyed from Twig just because it starts with "is" ;-) That's not really a solid approach and it will make the whole system much safer if we actually declare what's actually intended to be used from Twig in the future.

alexpott’s picture

Version: 11.x-dev » 11.3.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed c2e40edf741 to 11.x and 3acf8ea004c to 11.3.x. Thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • alexpott committed 3acf8ea0 on 11.3.x
    feat: #2595805 Leverage attributes in Twig Sandbox Policy
    
    By: @rgpublic...

  • alexpott committed c2e40edf on 11.x
    feat: #2595805 Leverage attributes in Twig Sandbox Policy
    
    By: @rgpublic...
geek-merlin’s picture

Thanks everyone!

Status: Fixed » Closed (fixed)

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