Resource Limits
New in version 1.4.0
For deployments where template authors are untrusted, you can set limits on some resources to avoid malicious templates from consuming too much memory or too many CPU cycles. Set one or more resource limits by subclassing a Liquid Environment.
from liquid import Environment
class MyEnvironment(Environment):
context_depth_limit = 30
local_namespace_limit = 2000
loop_iteration_limit = 1000
output_stream_limit = 15000
env = MyEnvironment()
template = env.from_string("""\
{% for x in (1..1000000) %}
{% for y in (1..1000000) %}
{{ x }},{{ y }}
{% endfor %}
{% endfor %}
""")
template.render()
# LoopIterationLimitError: loop iteration limit reached, on line 1
Context Depth Limit
The maximum number of times a render context can be extended or wrapped before a ContextDepthError
is raised.
This helps us guard against recursive use of the include
and render
tags. The default context depth limit is 30. Before Python Liquid version 1.4.0, a context depth limit of 30 was hard coded.
from liquid import Environment
from liquid import DictLoader
env = Environment(
loader=DictLoader(
{
"foo": "{% render 'bar' %}",
"bar": "{% render 'foo' %}",
}
)
)
template = env.from_string("{% render 'foo' %}")
template.render()
# ContextDepthError: maximum context depth reached, possible recursive render, on line 1
Local Namespace Limit
The maximum number of bytes (according to sys.getsizeof()
) allowed in a template's local namespace, per render, before a LocalNamespaceLimitError
exception is raised. Note that we only count the size of the local namespace values, not its keys.
The default local_namespace_limit
is None
, meaning there is no limit.
from liquid import Environment
class MyEnvironment(Environment):
local_namespace_limit = 50 # Very low, for demonstration purposes.
env = MyEnvironment()
template = env.from_string("""\
{% assign x = "Nunc est nulla, pellentesque ac dui id erat curae." %}
""")
template.render()
# LocalNamespaceLimitError: local namespace limit reached, on line 1
PyPy does not implement sys.getsizeof
. Instead of a size in bytes, when run with PyPy, local_namespace_limit
will degrade to being the number of distinct values in a template's local namespace.
You can customize the namespace size calculation by subclassing Context
and overriding get_size_of_locals()
. This example simply counts the number of entries in the namespace.
from liquid import Environment
from liquid import Context
from liquid.template import BoundTemplate
class MyRenderContext(Context):
def get_size_of_locals(self) -> int:
if not self.env.local_namespace_limit:
return 0
return len(self.locals) + self.local_namespace_size_carry
class MyBoundTemplate(BoundTemplate):
context_class = MyRenderContext
class MyEnvironment(Environment):
local_namespace_limit = 2 # XXX: very low, for demonstration purposes
template_class = MyBoundTemplate
env = MyEnvironment()
template = env.from_string(
"{% assign foo = 'hello' %}"
"{% assign bar = 'world' %}"
).render()
# raises a LocalNamespaceLimitError
template = env.from_string(
"{% assign foo = 'hello' %}"
"{% assign bar = 'world' %}"
"{% assign baz = '!' %}"
).render()
Changed in version 1.4.7: The default implementation of get_size_of_locals
no longer attempts to dedupe local namespace values as it would raise a TypeError
on unhashable types.
Loop Iteration Limit
The maximum number of loop iterations allowed before a LoopIterationLimitError
is raised.
The default loop_iteration_limit
is None
, meaning there is no limit.
from liquid import Environment
class MyEnvironment(Environment):
loop_iteration_limit = 999
env = MyEnvironment()
template = env.from_string("""\
{% for x in (1..100) %}
{% for y in (1..100) %}
{{ x }},{{ y }}
{% endfor %}
{% endfor %}
""")
template.render()
# LoopIterationLimitError: loop iteration limit reached, on line 1
Other built in tags that contribute to the loop iteration counter are render
, include
(when using their {% render 'thing' for some.thing %}
syntax) and tablerow
. If a partial template is rendered within a for
loop, the loop counter is carried over to the render context of the partial template.
Output Stream Limit
The maximum number of bytes that can be written to a template's output stream, per render, before an OutputStreamLimitError
exception is raised.
The default output_stream_limit
is None
, meaning there is no limit.
from liquid import Environment
class MyEnvironment(Environment):
output_stream_limit = 20 # Very low, for demonstration purposes.
env = MyEnvironment()
template = env.from_string("""\
{% if false %}
this is never rendered, so will not contribute the the output byte counter
{% endif %}
Hello, {{ you }}!
""")
template.render(you="World")
# '\nHello, World!\n'
template.render(you="something longer that exceeds our limit")
# OutputStreamLimitError: output stream limit reached, on line 4
String to Integer Limit
New in version 1.4.4
CVE-2020-10735 describes a potential denial of service attack by converting very long strings to integers. As of version 1.4.4, Python Liquid will raise a LiquidValueError
if an attempt is made to cast a long string to an integer.
Due to some unfortunate early Python Liquid design decisions, this is an interpreter-wide limit, unlike other limits described on this page, which are set per liquid.Environment
.
Python Liquid will look for a LIQUIDINTMAXSTRDIGITS
environment variable, giving the maximum number of digits allowed before attempting a str to int conversion. We will fall back to looking for PYTHONINTMAXSTRDIGITS
before defaulting to 4300
. Use LIQUIDINTMAXSTRDIGITS
when you want to use a lower limit for Liquid while keeping Python's limit higher.
When using patched versions of Python, Liquid will not honour sys.set_int_max_str_digits
. If Python's limit is lower than Liquid's, it will be possible to get a ValueError
exception instead of a LiquidValueError
when parsing Liquid templates.
Python Liquid's default limit helps guard against malicious templates authors. Be sure to validate user controlled inputs that might appear in a Liquid render context.