Skip to content

Commit 354ef3b

Browse files
committed
Merge branch '4.17' of https://github.com/craftcms/cms into 5.9
# Conflicts: # CHANGELOG-WIP.md # src/web/View.php # src/web/twig/Extension.php
2 parents e37c9aa + 90c79ce commit 354ef3b

File tree

5 files changed

+85
-38
lines changed

5 files changed

+85
-38
lines changed

CHANGELOG-WIP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- `entrify` commands now automatically assign newly-created channel/structure sections to “Categories” or “Tags” pages. ([#17779](https://github.com/craftcms/cms/pull/17779))
4343
- The `clear-cache` command now accepts a space-delimited list of cache IDs that should be cleared.
4444
- Compiled templates are now deleted by the `up` command rather than from `migrate` commands.
45+
- Added the `enableTwigSandbox` config setting. ([#18208](https://github.com/craftcms/cms/pull/18208))
4546
- Added the `useIdnaNontransitionalToUnicode` config setting. ([#17946](https://github.com/craftcms/cms/pull/17946))
4647
- The `maxCachedCloudImageSize` config setting is now set to `0` by default. ([#17997](https://github.com/craftcms/cms/pull/17997))
4748
- System message emails are now rendered using GitHub-flavored Markdown. ([#18058](https://github.com/craftcms/cms/discussions/18058))

src/config/GeneralConfig.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,24 @@ class GeneralConfig extends BaseConfig
12531253
*/
12541254
public bool $enableTemplateCaching = true;
12551255

1256+
/**
1257+
* @var bool Whether all Twig templates should be sandboxed.
1258+
*
1259+
* ::: code
1260+
* ```php Static Config
1261+
* ->enableTwigSandbox(false)
1262+
* ```
1263+
* ```shell Environment Override
1264+
* CRAFT_ENABLE_TWIG_SANDBOX=false
1265+
* ```
1266+
* :::
1267+
*
1268+
* @see enableTwigSandbox()
1269+
* @group Security
1270+
* @since 4.17.0
1271+
*/
1272+
public bool $enableTwigSandbox = false;
1273+
12561274
/**
12571275
* @var string The prefix that should be prepended to HTTP error status codes when determining the path to look for an error’s template.
12581276
*
@@ -4748,6 +4766,25 @@ public function enableTemplateCaching(bool $value = true): self
47484766
return $this;
47494767
}
47504768

4769+
/**
4770+
* Whether all Twig templates should be sandboxed.
4771+
*
4772+
* ```php
4773+
* ->enableTwigSandbox(false)
4774+
* ```
4775+
*
4776+
* @group Security
4777+
* @param bool $value
4778+
* @return self
4779+
* @see $enableTwigSandbox
4780+
* @since 4.17.0
4781+
*/
4782+
public function enableTwigSandbox(bool $value = true): self
4783+
{
4784+
$this->enableTwigSandbox = $value;
4785+
return $this;
4786+
}
4787+
47514788
/**
47524789
* The prefix that should be prepended to HTTP error status codes when determining the path to look for an error’s template.
47534790
*

src/web/View.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use craft\web\twig\FeExtension;
2727
use craft\web\twig\GlobalsExtension;
2828
use craft\web\twig\SafeHtml;
29+
use craft\web\twig\SecurityPolicy;
2930
use craft\web\twig\SinglePreloaderExtension;
3031
use craft\web\twig\TemplateLoader;
3132
use Illuminate\Support\Collection;
@@ -36,6 +37,7 @@
3637
use Twig\Error\SyntaxError as TwigSyntaxError;
3738
use Twig\Extension\CoreExtension;
3839
use Twig\Extension\ExtensionInterface;
40+
use Twig\Extension\SandboxExtension;
3941
use Twig\Extension\StringLoaderExtension;
4042
use Twig\Runtime\EscaperRuntime;
4143
use Twig\Template as TwigTemplate;
@@ -426,6 +428,9 @@ public function createTwig(): Environment
426428
/** @phpstan-ignore argument.type */
427429
$twig->getRuntime(EscaperRuntime::class)->addSafeClass($safeClass, ['html']);
428430

431+
// Even an empty security policy will prevent non-closures from being allowed as arrow functions
432+
$twig->addExtension(new SandboxExtension(new SecurityPolicy(), Craft::$app->getConfig()->getGeneral()->enableTwigSandbox));
433+
429434
$twig->addExtension(new StringLoaderExtension());
430435
$twig->addExtension(new Extension($this, $twig));
431436

src/web/twig/Extension.php

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class Extension extends AbstractExtension implements GlobalsInterface
101101
*/
102102
public static function arraySome(TwigEnvironment $env, $array, $arrow)
103103
{
104-
self::checkArrowFunction($arrow, 'has some', 'operator');
104+
CoreExtension::checkArrow($env, $arrow, 'has some', 'operator');
105105
return CoreExtension::arraySome($env, $array, $arrow);
106106
}
107107

@@ -110,38 +110,10 @@ public static function arraySome(TwigEnvironment $env, $array, $arrow)
110110
*/
111111
public static function arrayEvery(TwigEnvironment $env, $array, $arrow)
112112
{
113-
self::checkArrowFunction($arrow, 'has every', 'operator');
113+
CoreExtension::checkArrow($env, $arrow, 'has every', 'operator');
114114
return CoreExtension::arrayEvery($env, $array, $arrow);
115115
}
116116

117-
/**
118-
* Called by:
119-
* - has every (operator)
120-
* - has some (operator)
121-
* - |filter
122-
* - |find
123-
* - |map
124-
* - |reduce
125-
* - |sort
126-
*/
127-
private static function checkArrowFunction(mixed $arrow, string $thing, string $type): void
128-
{
129-
if (
130-
is_string($arrow) &&
131-
in_array(ltrim(strtolower($arrow), '\\'), [
132-
'system',
133-
'passthru',
134-
'exec',
135-
'file_get_contents',
136-
'file_put_contents',
137-
'popen',
138-
'call_user_func',
139-
])
140-
) {
141-
throw new RuntimeError(sprintf('The "%s" %s does not support passing "%s".', $thing, $type, $arrow));
142-
}
143-
}
144-
145117
/**
146118
* @var View|null
147119
*/
@@ -257,7 +229,7 @@ public function getFilters(): array
257229
new TwigFilter('filterByValue', [ArrayHelper::class, 'where'], ['deprecation_info' => new DeprecatedCallableInfo('craftcms/cms', '3.5.0', 'where')]),
258230
new TwigFilter('firstWhere', [ArrayHelper::class, 'firstWhere']),
259231
new TwigFilter('flatten', [Arr::class, 'flatten']),
260-
new TwigFilter('group', [$this, 'groupFilter']),
232+
new TwigFilter('group', [$this, 'groupFilter'], ['needs_environment' => true]),
261233
new TwigFilter('hash', [$this, 'hashFilter']),
262234
new TwigFilter('httpdate', [$this, 'httpdateFilter'], ['needs_environment' => true]),
263235
new TwigFilter('id', [Html::class, 'id']),
@@ -537,7 +509,7 @@ public function snakeFilter(mixed $string): string
537509
*/
538510
public function sortFilter(TwigEnvironment $env, iterable $array, string|callable|null $arrow = null): array
539511
{
540-
self::checkArrowFunction($arrow, 'sort', 'filter');
512+
CoreExtension::checkArrow($env, $arrow, 'sort', 'filter');
541513
return CoreExtension::sort($env, $array, $arrow);
542514
}
543515

@@ -554,7 +526,7 @@ public function sortFilter(TwigEnvironment $env, iterable $array, string|callabl
554526
*/
555527
public function reduceFilter(TwigEnvironment $env, mixed $array, mixed $arrow, mixed $initial = null): mixed
556528
{
557-
self::checkArrowFunction($arrow, 'reduce', 'filter');
529+
CoreExtension::checkArrow($env, $arrow, 'reduce', 'filter');
558530
return CoreExtension::reduce($env, $array, $arrow, $initial);
559531
}
560532

@@ -570,7 +542,7 @@ public function reduceFilter(TwigEnvironment $env, mixed $array, mixed $arrow, m
570542
*/
571543
public function mapFilter(TwigEnvironment $env, mixed $array, mixed $arrow = null): array
572544
{
573-
self::checkArrowFunction($arrow, 'map', 'filter');
545+
CoreExtension::checkArrow($env, $arrow, 'map', 'filter');
574546
return CoreExtension::map($env, $array, $arrow);
575547
}
576548

@@ -695,7 +667,7 @@ public function timestampFilter(mixed $value, ?string $format = null, bool $with
695667
*/
696668
public function findFilter(TwigEnvironment $env, $array, $arrow): mixed
697669
{
698-
self::checkArrowFunction($arrow, 'find', 'filter');
670+
CoreExtension::checkArrow($env, $arrow, 'find', 'filter');
699671
return CoreExtension::find($env, $array, $arrow);
700672
}
701673

@@ -1172,7 +1144,7 @@ public function encencFilter(mixed $str): string
11721144
*/
11731145
public function filterFilter(TwigEnvironment $env, iterable $arr, ?callable $arrow = null): array
11741146
{
1175-
self::checkArrowFunction($arrow, 'filter', 'filter');
1147+
CoreExtension::checkArrow($env, $arrow, 'filter', 'filter');
11761148

11771149
/** @var array|Traversable $arr */
11781150
if ($arrow === null) {
@@ -1194,14 +1166,15 @@ public function filterFilter(TwigEnvironment $env, iterable $arr, ?callable $arr
11941166
/**
11951167
* Groups an array by the results of an arrow function, or value of a property.
11961168
*
1169+
* @param TwigEnvironment $env
11971170
* @param iterable $arr
11981171
* @param callable|string $arrow The arrow function or property name that determines the group the item should be grouped in
11991172
* @return array[] The grouped items
12001173
* @throws RuntimeError if $arr is not of type array or Traversable
12011174
*/
1202-
public function groupFilter(iterable $arr, callable|string $arrow): array
1175+
public function groupFilter(TwigEnvironment $env, iterable $arr, callable|string $arrow): array
12031176
{
1204-
self::checkArrowFunction($arrow, 'group', 'filter');
1177+
CoreExtension::checkArrow($env, $arrow, 'group', 'filter');
12051178

12061179
$groups = [];
12071180

src/web/twig/SecurityPolicy.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
/**
3+
* @link https://craftcms.com/
4+
* @copyright Copyright (c) Pixel & Tonic, Inc.
5+
* @license https://craftcms.github.io/license/
6+
*/
7+
8+
namespace craft\web\twig;
9+
10+
use Twig\Sandbox\SecurityPolicyInterface;
11+
12+
/**
13+
* Security policy
14+
*
15+
* @author Pixel & Tonic, Inc. <[email protected]>
16+
* @since 4.17.0
17+
*/
18+
class SecurityPolicy implements SecurityPolicyInterface
19+
{
20+
public function checkSecurity($tags, $filters, $functions): void
21+
{
22+
}
23+
24+
public function checkMethodAllowed($obj, $method): void
25+
{
26+
}
27+
28+
public function checkPropertyAllowed($obj, $property): void
29+
{
30+
}
31+
}

0 commit comments

Comments
 (0)