Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9f2a5f7
Disables PHP by default when calling Antlers::parse
JohnathonKoster Feb 26, 2026
a5b882e
Update PhpDisabledTest.php
JohnathonKoster Feb 26, 2026
f8564d6
prevent method calls
jasonvarga Feb 26, 2026
a5aba7d
remove parseUserContent
jasonvarga Feb 26, 2026
b30923b
Default Antlers runtime to user-content safety mode.
jasonvarga Feb 26, 2026
632b923
Rename Antlers parse trust flag and propagate trusted contexts.
jasonvarga Feb 26, 2026
d849c8e
Align Antlers runtime tests with explicit trust semantics.
jasonvarga Feb 26, 2026
61cf4e5
wip
jasonvarga Feb 26, 2026
114e50c
wip
jasonvarga Feb 26, 2026
d53bf0c
Propagate ambient trust mode in tag pair reparsing.
jasonvarga Feb 26, 2026
03365ff
Block tags and modifiers (aside from a handful) unless in trusted parses
jasonvarga Feb 26, 2026
b0cac8e
test tags through the parser, not directly through node processor
jasonvarga Feb 26, 2026
75dda54
populate the default allowed tags and modifiers
jasonvarga Feb 26, 2026
8428bac
doesnt need to change
jasonvarga Feb 26, 2026
66c11e2
parser tests with tags assume trusted
jasonvarga Feb 26, 2026
0691766
partial modifier parses a file on disk so its trusted
jasonvarga Feb 26, 2026
8dc34cb
use modifiers in test that arent blocked
jasonvarga Feb 26, 2026
30b234f
fix test by trusting
jasonvarga Feb 27, 2026
0e733ab
join is ok
jasonvarga Feb 27, 2026
76bb1ce
antlers modifier can opt into trusted mode but only if already in a t…
jasonvarga Feb 27, 2026
55722e5
this didnt need to change
jasonvarga Feb 27, 2026
f90ed37
wip
jasonvarga Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Entries/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ public function autoGeneratedTitle()

// Since the slug is generated from the title, we'll avoid augmenting
// the slug which could result in an infinite loop in some cases.
$title = $this->withLocale($this->site()->lang(), fn () => (string) Antlers::parseUserContent($format, $this->augmented()->except('slug')->all()));
$title = $this->withLocale($this->site()->lang(), fn () => (string) Antlers::parse($format, $this->augmented()->except('slug')->all()));

return trim($title);
}
Expand All @@ -1065,7 +1065,7 @@ private function resolvePreviewTargetUrl($format)
}, $format);
}

return (string) Antlers::parseUserContent($format, array_merge($this->routeData(), [
return (string) Antlers::parse($format, array_merge($this->routeData(), [
'config' => Cascade::config(),
'site' => $this->site(),
'uri' => $this->uri(),
Expand Down
5 changes: 2 additions & 3 deletions src/Facades/Antlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
/**
* @method static Parser parser()
* @method static mixed usingParser(Parser $parser, \Closure $callback)
* @method static AntlersString parse(string $str, array $variables = [])
* @method static AntlersString parseUserContent(string $str, array $variables = [])
* @method static string parseLoop(string $content, array $data, bool $supplement = true, array $context = [])
* @method static AntlersString parse(string $str, array $variables = [], bool $trusted = false)
* @method static string parseLoop(string $content, array $data, bool $supplement = true, array $context = [], bool $trusted = false)
* @method static array identifiers(string $content)
*
* @see \Statamic\View\Antlers\Antlers
Expand Down
12 changes: 6 additions & 6 deletions src/Facades/Endpoint/Parse.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ class Parse
* @param string $str String to parse
* @param array $variables Variables to use
* @param array $context Contextual variables to also use
* @param bool $php Whether PHP should be allowed
* @param bool $trusted Whether the template should be treated as trusted
* @return string
*/
public function template($str, $variables = [], $context = [], $php = false)
public function template($str, $variables = [], $context = [], $trusted = false)
{
return Antlers::parse($str, $variables, $context, $php);
return Antlers::parse($str, array_merge($variables, $context), $trusted);
}

/**
Expand All @@ -32,12 +32,12 @@ public function template($str, $variables = [], $context = [], $php = false)
* @param array $data Variables to use, in a multidimensional array
* @param bool $supplement Whether to supplement with contextual values
* @param array $context Contextual variables to also use
* @param bool $php Whether PHP should be allowed
* @param bool $trusted Whether the template should be treated as trusted
* @return string
*/
public function templateLoop($content, $data, $supplement = true, $context = [], $php = false)
public function templateLoop($content, $data, $supplement = true, $context = [], $trusted = false)
{
return Antlers::parseLoop($content, $data, $supplement, $context, $php);
return Antlers::parseLoop($content, $data, $supplement, $context, $trusted);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/Facades/Parse.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
use Statamic\View\Antlers\AntlersString;

/**
* @method static AntlersString template($str, $variables = [], $context = [], $php = false)
* @method static string templateLoop($content, $data, $supplement = true, $context = [], $php = false)
* @method static AntlersString template($str, $variables = [], $context = [], $trusted = false)
* @method static string templateLoop($content, $data, $supplement = true, $context = [], $trusted = false)
* @method static array YAML($str)
* @method static array frontMatter($string)
* @method static mixed env($val)
Expand Down
2 changes: 1 addition & 1 deletion src/Forms/Email.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ protected function parseConfig(array $config)
return collect($config)->map(function ($value) {
$value = Parse::env($value); // deprecated

return (string) Antlers::parseUserContent($value, array_merge(
return (string) Antlers::parse($value, array_merge(
['config' => Cascade::config()],
$this->getGlobalsData(),
$this->submissionData,
Expand Down
7 changes: 5 additions & 2 deletions src/Modifiers/CoreModifiers.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Statamic\Support\Dumper;
use Statamic\Support\Html;
use Statamic\Support\Str;
use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;
use Stringy\StaticStringy as Stringy;

class CoreModifiers extends Modifier
Expand Down Expand Up @@ -119,7 +120,9 @@ public function ampersandList($value, $params)
*/
public function antlers($value, $params, $context)
{
return (string) Antlers::parse($value, $context);
$trusted = Arr::get($params, 0) === 'trusted' && ! GlobalRuntimeState::$isEvaluatingUserData;

return (string) Antlers::parse($value, $context, $trusted);
}

/**
Expand Down Expand Up @@ -1872,7 +1875,7 @@ public function partial($value, $params, $context)

$partial = 'partials/'.$name.'.html';

return Parse::template(File::disk('resources')->get($partial), $value);
return Parse::template(File::disk('resources')->get($partial), $value, trusted: true);
}

/**
Expand Down
93 changes: 93 additions & 0 deletions src/Providers/ViewServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,99 @@ private function registerAntlers()
$runtimeConfig->guardedContentVariablePatterns = config('statamic.antlers.guardedContentVariables', []);
$runtimeConfig->guardedContentTagPatterns = config('statamic.antlers.guardedContentTags', []);
$runtimeConfig->guardedContentModifiers = config('statamic.antlers.guardedContentModifiers', []);
$runtimeConfig->allowedContentTagPatterns = config('statamic.antlers.allowedContentTags', [
'obfuscate:*',
'trans:*',
'trans_choice:*',
'widont:*',
]);
$runtimeConfig->allowedContentModifiers = config('statamic.antlers.allowedContentModifiers', [
'add_query_param',
'add_slashes',
'ascii',
'at',
'background_position',
'bool_string',
'camelize',
'cdata',
'ceil',
'collapse_whitespace',
'count_substring',
'dashify',
'decode',
'deslugify',
'divide',
'ends_with',
'ensure_left',
'ensure_right',
'entities',
'explode',
'extension',
'floor',
'format',
'format_number',
'format_translated',
'has_lower_case',
'has_upper_case',
'headline',
'hex_to_rgb',
'insert',
'is_alpha',
'is_alphanumeric',
'is_blank',
'is_email',
'is_external_url',
'is_json',
'is_lowercase',
'is_numeric',
'is_uppercase',
'is_url',
'join',
'kebab',
'lcfirst',
'localize',
'upper',
'lower',
'md5',
'mod',
'multiply',
'obfuscate',
'obfuscate_email',
'parse_url',
'pathinfo',
'rawurlencode',
'remove_left',
'remove_query_param',
'remove_right',
'replace',
'round',
'safe_truncate',
'sanitize',
'slugify',
'snake',
'starts_with',
'str_pad',
'str_pad_both',
'str_pad_left',
'str_pad_right',
'strip_tags',
'studly',
'subtract',
'substr',
'sum',
'swap_case',
'title',
'to_bool',
'to_string',
'trans',
'trans_choice',
'trim',
'truncate',
'ucfirst',
'urldecode',
'urlencode',
'widont',
]);
$runtimeConfig->allowPhpInUserContent = config('statamic.antlers.allowPhpInContent', false);
$runtimeConfig->allowMethodsInUserContent = config('statamic.antlers.allowMethodsInContent', false);

Expand Down
9 changes: 7 additions & 2 deletions src/Tags/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Statamic\Facades\Antlers;
use Statamic\Support\Arr;
use Statamic\Support\Traits\Hookable;
use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;

abstract class Tags
{
Expand Down Expand Up @@ -207,8 +208,10 @@ public function parse($data = [])
}

return Antlers::usingParser($this->parser, function ($antlers) use ($data) {
$trusted = ! GlobalRuntimeState::$isEvaluatingUserData;

return $antlers
->parse($this->content, array_merge($this->context->all(), $data))
->parse($this->content, array_merge($this->context->all(), $data), $trusted)
->withoutExtractions();
});
}
Expand Down Expand Up @@ -244,8 +247,10 @@ public function parseLoop($data, $supplement = true)
}

return Antlers::usingParser($this->parser, function ($antlers) use ($data, $supplement) {
$trusted = ! GlobalRuntimeState::$isEvaluatingUserData;

return $antlers
->parseLoop($this->content, $data, $supplement, $this->context->all())
->parseLoop($this->content, $data, $supplement, $this->context->all(), $trusted)
->withoutExtractions();
});
}
Expand Down
21 changes: 9 additions & 12 deletions src/View/Antlers/Antlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,16 @@ public function usingParser(Parser $parser, Closure $callback)
return $contents;
}

public function parse($str, $variables = [])
public function parse($str, $variables = [], $trusted = false)
{
return $this->parser()->parse($str, $variables);
}

public function parseUserContent($str, $variables = [])
{
$isEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
GlobalRuntimeState::$isEvaluatingUserData = true;
$parser = $this->parser();
$previousState = GlobalRuntimeState::$isEvaluatingUserData;
GlobalRuntimeState::$isEvaluatingUserData = ! $trusted;

try {
return $this->parser()->parse($str, $variables);
return $parser->parse($str, $variables);
} finally {
GlobalRuntimeState::$isEvaluatingUserData = $isEvaluatingUserData;
GlobalRuntimeState::$isEvaluatingUserData = $previousState;
}
}

Expand All @@ -51,11 +47,12 @@ public function parseUserContent($str, $variables = [])
* @param array $data
* @param bool $supplement
* @param array $context
* @param bool $trusted
* @return string
*/
public function parseLoop($content, $data, $supplement = true, $context = [])
public function parseLoop($content, $data, $supplement = true, $context = [], $trusted = false)
{
return new AntlersLoop($this->parser(), $content, $data, $supplement, $context);
return new AntlersLoop($this->parser(), $content, $data, $supplement, $context, $trusted);
}

public function identifiers(string $content): array
Expand Down
18 changes: 17 additions & 1 deletion src/View/Antlers/AntlersLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,40 @@

namespace Statamic\View\Antlers;

use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;

class AntlersLoop extends AntlersString
{
protected $parser;
protected $string;
protected $variables;
protected $supplement;
protected $context;
protected $trusted;

public function __construct($parser, $string, $variables, $supplement, $context)
public function __construct($parser, $string, $variables, $supplement, $context, $trusted = false)
{
$this->parser = $parser;
$this->string = $string;
$this->variables = $variables;
$this->supplement = $supplement;
$this->context = $context;
$this->trusted = $trusted;
}

public function __toString()
{
$previousIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
GlobalRuntimeState::$isEvaluatingUserData = ! $this->trusted;

try {
return $this->renderLoopContent();
} finally {
GlobalRuntimeState::$isEvaluatingUserData = $previousIsEvaluatingUserData;
}
}

private function renderLoopContent()
{
$total = count($this->variables);
$i = 0;
Expand Down
18 changes: 16 additions & 2 deletions src/View/Antlers/Language/Runtime/GlobalRuntimeState.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class GlobalRuntimeState
*
* @var bool
*/
public static $isEvaluatingUserData = false;
public static $isEvaluatingUserData = true;

public static $isEvaluatingData = false;

Expand Down Expand Up @@ -163,6 +163,13 @@ public static function mergeTagRuntimeAssignments($assignments)
*/
public static $bannedContentTagPaths = [];

/**
* A list of all allowed content tag paths.
*
* @var string[]
*/
public static $allowedContentTagPaths = [];

/**
* A list of all invalid modifier paths.
*
Expand All @@ -177,6 +184,13 @@ public static function mergeTagRuntimeAssignments($assignments)
*/
public static $bannedContentModifierPaths = [];

/**
* A list of all allowed content modifier paths.
*
* @var string[]
*/
public static $allowedContentModifierPaths = [];

/**
* Controls if PHP is evaluated in user content.
*
Expand Down Expand Up @@ -233,7 +247,7 @@ public static function resetGlobalState()
self::$yieldCount = 0;
self::$yieldStacks = [];
self::$abandonedNodes = [];
self::$isEvaluatingUserData = false;
self::$isEvaluatingUserData = true;
self::$isEvaluatingData = false;
self::$userContentEvalState = null;

Expand Down
Loading