Skip to main content
Sandbox mode restricts what template authors can do inside a .lex file. Use it whenever you render templates that were written or modified by end users — for example, email newsletter editors, report builders, or any CMS feature that lets users author their own markup. Without sandbox mode, a template author can call arbitrary PHP functions, use raw echo, or embed #php blocks. Sandbox mode removes those capabilities so you can safely compile and render user-supplied content.

Enabling sandbox mode

Call enableSandbox() on your Lexer instance. You can optionally pass a SandboxConfig object to control exactly what is restricted.
use Wik\Lexer\Lexer;
use Wik\Lexer\Security\SandboxConfig;

// Secure defaults — raw echo forbidden, no PHP calls, no custom directives
$lexer = (new Lexer())
    ->paths([__DIR__ . '/views'])
    ->enableSandbox();
To use a specific preset or to customise restrictions, pass a SandboxConfig:
$lexer = (new Lexer())
    ->paths([__DIR__ . '/views'])
    ->enableSandbox()
    ->setSandboxConfig(
        SandboxConfig::secure()
            ->withAllowedFunctions(['date', 'number_format', 'strtolower'])
    );

Configuration presets

SandboxConfig ships with two factory methods covering the most common needs.
Starts with no restrictions except the always-blocked functions (see below). Raw echo is allowed, custom directives are allowed, and all PHP functions are callable.Use this preset when you want to track the always-blocked list automatically without adding any additional restrictions yourself.
use Wik\Lexer\Security\SandboxConfig;

$config = SandboxConfig::permissive();

$lexer->enableSandbox()->setSandboxConfig($config);
SettingValue
Raw echo {!! !!}Allowed
PHP function callsAll allowed (except always-blocked)
Custom directivesAllowed
#php blocksAllowed

Fluent modifiers

SandboxConfig is immutable. Each modifier returns a new instance.

withAllowedFunctions(array $fns): static

Provides an explicit whitelist of PHP functions that template expressions may call. Any function not in this list throws a TemplateSyntaxException at compile time.
SandboxConfig::secure()
    ->withAllowedFunctions([
        'date',
        'number_format',
        'strtolower',
        'strtoupper',
        'strlen',
        'substr',
        'trim',
    ]);
Passing null means all functions are allowed (subject to the always-blocked list). Passing an empty array [] means no function calls are permitted at all.

withRawEcho(bool $allow): static

Controls whether {!! $expr !!} raw output is permitted in the template. In SandboxConfig::secure() this defaults to false.
// Explicitly re-enable raw echo on a secure config
$config = SandboxConfig::secure()->withRawEcho(true);

// Explicitly disable it on a permissive config
$config = SandboxConfig::permissive()->withRawEcho(false);

withAllowedDirectives(array $directives): static

Whitelists specific custom directive names when allowCustomDirectives is false. This lets you allow selected trusted directives while still blocking all others.
SandboxConfig::secure()
    ->withAllowedDirectives(['datetime', 'currency']);

withCustomDirectives(bool $allow): static

Toggles whether any custom directive registered via $lexer->directive() may be used in the sandboxed template.

Always-blocked functions

The following functions are always forbidden, regardless of any SandboxConfig setting or whitelist. They are blocked at the ExpressionValidator level and cannot be re-enabled:
eval
assert
create_function
preg_replace_callback_array
The backtick shell execution operator (`) is also always blocked.
The always-blocked list is enforced whenever sandbox mode is active. SandboxConfig::permissive() still blocks these functions — it only removes the additional restrictions on raw echo, custom directives, and function whitelisting. The always-blocked list cannot be overridden by any sandbox preset.

What sandbox mode restricts

FeaturePermissiveSecure
#php / #endphp blocksAllowedBlocked
Raw echo {!! !!}AllowedBlocked
Arbitrary PHP function callsAllowedBlocked (unless whitelisted)
new keyword in expressionsAllowedBlocked
Custom directivesAllowedBlocked
Always-blocked functionsBlockedBlocked
Backtick operatorBlockedBlocked

Example: user-submitted newsletter templates

The following shows a realistic setup for a newsletter editor where users can personalise their own email templates.
1

Configure the lexer with secure sandbox

use Wik\Lexer\Lexer;
use Wik\Lexer\Security\SandboxConfig;
use Wik\Lexer\Loader\MemoryLoader;

$config = SandboxConfig::secure()
    ->withAllowedFunctions([
        'strtoupper',
        'strtolower',
        'number_format',
        'date',
        'substr',
        'strlen',
        'trim',
    ]);

$lexer = (new Lexer())
    ->enableSandbox()
    ->setSandboxConfig($config);
2

Load the user-supplied template string

use Wik\Lexer\Loader\MemoryLoader;

$loader = new MemoryLoader();
$loader->set('newsletter', $templateStringFromDatabase);
// Attach the loader to the lexer (see Loaders documentation)
3

Render with subscriber data

echo $lexer->render('newsletter', [
    'first_name'    => $subscriber->firstName,
    'unsubscribe'   => $subscriber->unsubscribeUrl,
    'offer_expires' => $campaign->expiresAt,
]);
A user-authored template like the following compiles and renders safely:
Hi {{ strtoupper($first_name) }},

Your exclusive offer expires on {{ date('d M Y', strtotime($offer_expires)) }}.

<a href="{{ $unsubscribe }}">Unsubscribe</a>
If the user attempts to embed a dangerous call, the compiler throws a TemplateSyntaxException at compile time — before any output is produced:
<!-- This would throw TemplateSyntaxException -->
{{ file_get_contents('/etc/passwd') }}
{{ exec('id') }}
{!! $dangerousHtml !!}
Sandbox mode uses regex-based heuristics to detect function calls — it is not a full PHP parser. For a fully isolated execution environment with zero risk of escape, run template rendering in a separate PHP process or container.

Build docs developers (and LLMs) love