Add MAX_STRING_SIZE limit to sandbox string multiplication

SandboxedEnvironment limits range() via MAX_RANGE to prevent DoS from
large sequences, but does not limit string multiplication. A template
expression like {{ "A" * 10**9 }} allocates 1GB of memory instantly.

Add MAX_STRING_SIZE (default 1,000,000) and a safe_mul function that
checks result size before performing string repetition. Wire it into
the default binop_table and intercepted_binops so the sandbox
intercepts * operations by default.

Normal arithmetic multiplication, small string repetition, and list
multiplication are all unaffected. Only str * int exceeding the limit
is blocked.
This commit is contained in:
Koda Reef 2026-03-22 22:03:12 +00:00
parent 5ef70112a1
commit 8bb276db8b

View File

@ -24,6 +24,9 @@ F = t.TypeVar("F", bound=t.Callable[..., t.Any])
#: maximum number of items a range may produce
MAX_RANGE = 100000
#: maximum size of a string that can be produced by the ``*`` operator
MAX_STRING_SIZE = 1000000
#: Unsafe function attributes.
UNSAFE_FUNCTION_ATTRIBUTES: set[str] = set()
@ -99,6 +102,25 @@ def safe_range(*args: int) -> range:
return rng
def safe_mul(left: t.Any, right: t.Any) -> t.Any:
"""A multiplication that prevents creating strings larger than
MAX_STRING_SIZE via the ``*`` operator.
"""
if isinstance(left, str) and isinstance(right, int):
if right > 0 and len(left) * right > MAX_STRING_SIZE:
raise OverflowError(
"String repetition too large. The sandbox blocks strings"
f" larger than MAX_STRING_SIZE ({MAX_STRING_SIZE})."
)
elif isinstance(right, str) and isinstance(left, int):
if left > 0 and len(right) * left > MAX_STRING_SIZE:
raise OverflowError(
"String repetition too large. The sandbox blocks strings"
f" larger than MAX_STRING_SIZE ({MAX_STRING_SIZE})."
)
return operator.mul(left, right)
def unsafe(f: F) -> F:
"""Marks a function or method as unsafe.
@ -193,7 +215,7 @@ class SandboxedEnvironment(Environment):
default_binop_table: dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"*": safe_mul,
"/": operator.truediv,
"//": operator.floordiv,
"**": operator.pow,
@ -222,7 +244,7 @@ class SandboxedEnvironment(Environment):
#: interested in.
#:
#: .. versionadded:: 2.6
intercepted_binops: frozenset[str] = frozenset()
intercepted_binops: frozenset[str] = frozenset(["*"])
#: a set of unary operators that should be intercepted. Each operator
#: that is added to this set (empty by default) is delegated to the