Letting users write templates (emails, themes, notifications) is a server-side template injection risk without a sandbox.
If template source comes from users, a normal Environment is dangerous: Jinja expressions can walk Python's object graph, for example {{ ''.__class__.__mro__[1].__subclasses__() }}, and reach functions that read files or run commands. This is server-side template injection (SSTI).
jinja2.sandbox.SandboxedEnvironment intercepts attribute access and calls:
- underscore attributes such as
__class__and__globals__, and internal function and frame attributes, are blocked: they evaluate to a special undefined value that raisesSecurityErroras soon as it is used, and calling unsafe callables raises it too range()is capped (MAX_RANGE, 100000 items) to stop trivially huge loopsImmutableSandboxedEnvironmentadditionally blocks methods that mutate lists, dicts and sets, likeappendorupdate
You can customize policy by subclassing and overriding is_safe_attribute(obj, attr, value) and is_safe_callable(obj), or mark your own functions with @unsafe from the sandbox module.
What the sandbox does not do:
- protect against resource exhaustion, such as nested loops or huge strings; enforce timeouts and output size limits yourself, ideally in a separate process
- make unsafe objects safe: anything you pass into the context is reachable, so pass plain data (dicts, strings, numbers), never ORM objects with
delete()methods or your app's config - escape output: autoescaping is still a separate setting
The safest design is still to not let users write templates at all. If you must, combine the sandbox with minimal context data, autoescaping and resource limits.
Example
from jinja2.sandbox import ImmutableSandboxedEnvironment, SandboxedEnvironment
from jinja2.exceptions import SecurityError
from jinja2 import Environment
evil = "{{ ''.__class__.__mro__[1].__subclasses__()|length > 0 }}"
print(Environment().from_string(evil).render()) # True: object graph reachable!
sandbox = SandboxedEnvironment(autoescape=True)
try:
print(sandbox.from_string(evil).render())
except Exception as e:
print(type(e).__name__) # SecurityError
user_template = "Hi {{ user.name }}, you have {{ count }} new messages"
print(sandbox.from_string(user_template).render(user={"name": "Ann"}, count=3))
# Hi Ann, you have 3 new messages
immutable = ImmutableSandboxedEnvironment()
try:
immutable.from_string("{{ items.append(4) }}").render(items=[1, 2, 3])
except SecurityError as e:
print("blocked:", e) # blocked: access to attribute 'append' of 'list' object is unsafe.Remember
- SandboxedEnvironment blocks unsafe attributes and calls in untrusted templates
- ImmutableSandboxedEnvironment also blocks mutating container methods
- The sandbox doesn't limit CPU or memory, and can't protect unsafe objects you pass in
Watch out for
- Rendering user-supplied template source with a normal Environment (SSTI).