Module

sandbox

Sandboxed template execution — defense-in-depth for risky templates.

SandboxedEnvironmentrestricts attribute access, callable types, range sizes, and imports to reduce the blast radius of a template that attempts to reach out of its intended context. It is not an isolation boundary: it does not restrict CPU, memory, or wall-clock use; it does not sandbox objects you place in the render context; and the Python object model is large enough that novel escapes are possible.

Never render fully untrusted template source against a production context with this sandbox alone. Combine it with OS-level isolation (process boundary, container, seccomp, WASM, etc.), a curated render context of primitive types, and a wall-clock timeout. SeeSECURITY.mdfor the full threat model and hardening guidance.

Usage::

from kida import SandboxedEnvironment

env = SandboxedEnvironment()
# Templates cannot access __dunder__ attributes, call unsafe functions,
# or import modules.
env.from_string("{{ user.name }}").render(user=user)  # OK
env.from_string("{{ user.__class__ }}").render(user=user)  # blocked

Custom policy::

from kida.sandbox import SandboxPolicy, SandboxedEnvironment

policy = SandboxPolicy(
    allowed_attributes={"name", "title", "items", "keys", "values"},
    blocked_types={type, type(lambda: 0)},
)
env = SandboxedEnvironment(sandbox_policy=policy)

Classes

SandboxPolicy 8
Configuration for sandbox restrictions.

Configuration for sandbox restrictions.

Attributes

Name Type Description
allowed_attributes frozenset[str] | None

If set, only these attribute names are accessible (plus safe collection methods). If None, all non-blocked attributes are allowed.

blocked_attributes frozenset[str]

Additional attribute names to block (merged with the built-in blocked set).

blocked_types frozenset[type]

Object types that cannot be accessed at all. Default blocks function, type, and code objects.

allow_import bool

Whether__import__is available. Default: False.

allow_mutating_methods bool

Whether mutating collection methods (append, pop, clear, etc.) are accessible. Default: False.

allow_calling frozenset[str] | None

Set of type names whose instances may be called. If None (default), non-blocked callable types are allowed. Python functions are callable only when registered as environment globals or compiled by Kida. Pass an empty frozenset to block all calls.

max_output_size int | None

Maximum render output length in characters. None means unlimited.

max_range int

Maximum range() size. Default: 10000.

SecurityError 1
Raised when a sandbox policy violation is detected. Includes an error code and actionable suggesti…

Raised when a sandbox policy violation is detected.

Includes an error code and actionable suggestion for resolving the violation. All security errors inherit from TemplateError, so they are caught byexcept TemplateError.

Example::

SecurityError: Access to attribute '__class__' is blocked by sandbox policy
  Hint: Remove the attribute access, or add '__class__' to
        SandboxPolicy(allowed_attributes=...) if you trust this template.
  Docs: https://lbliii.github.io/kida/docs/errors/#k-sec-001

Methods

Internal Methods 1
__init__ 3
def __init__(self, message: str, *, code: ErrorCode | None = None, suggestion: str | None = None)
Parameters
Name Type Description
message
code Default:None
suggestion Default:None
SandboxedEnvironment 3
Environment subclass that enforces sandbox restrictions. All templates compiled by this environmen…

Environment subclass that enforces sandbox restrictions.

All templates compiled by this environment use sandboxed attribute access, restricted builtins, and policy-enforced limits.

The sandbox intercepts:

  • Attribute access (blocks dunder, unsafe types)
  • __import__(disabled by default)
  • range()(size-limited)
  • Template output (optional size limit)

Example::

env = SandboxedEnvironment()
tmpl = env.from_string("{{ user.name }}")
tmpl.render(user={"name": "Alice"})  # OK

tmpl = env.from_string("{{ user.__class__.__mro__ }}")
tmpl.render(user="hello")  # raises SecurityError

Custom policy::

from kida.sandbox import SandboxPolicy
policy = SandboxPolicy(allowed_attributes=frozenset({"name", "email"}))
env = SandboxedEnvironment(sandbox_policy=policy)

Attributes

Name Type Description
sandbox_policy SandboxPolicy | None
_sandbox_marker object

Methods

Internal Methods 1
__post_init__ 0
def __post_init__(self) -> None

Functions

_no_trusted_callables 0 tuple[object, ...]
Return the empty trusted-callable set for direct helper use.
def _no_trusted_callables() -> tuple[object, ...]
Returns
tuple[object, ...]
_is_attr_blocked 2 bool
Check if an attribute name is blocked by the policy.
def _is_attr_blocked(name: str, policy: SandboxPolicy) -> bool
Parameters
Name Type Description
name str
policy SandboxPolicy
Returns
bool
_is_type_blocked 2 bool
Check if an object's type is blocked.
def _is_type_blocked(obj: object, policy: SandboxPolicy) -> bool
Parameters
Name Type Description
obj object
policy SandboxPolicy
Returns
bool
_make_sandboxed_getattr 1
Create a sandboxed version of safe_getattr.
def _make_sandboxed_getattr(policy: SandboxPolicy)
Parameters
Name Type Description
policy SandboxPolicy
_make_sandboxed_getattr_none 1
Create a sandboxed version of getattr_preserve_none.
def _make_sandboxed_getattr_none(policy: SandboxPolicy)
Parameters
Name Type Description
policy SandboxPolicy
_make_sandboxed_range 1
Create a range() that enforces max_range.
def _make_sandboxed_range(policy: SandboxPolicy)
Parameters
Name Type Description
policy SandboxPolicy
_make_sandboxed_call 4
Create a call interceptor that blocks unsafe callables. When ``policy.allow_ca…
def _make_sandboxed_call(policy: SandboxPolicy, *, namespace: dict[str, Any] | None = None, sandbox_marker: object = _DEFAULT_SANDBOX_MARKER, trusted_callables: Callable[[], Iterable[object]] = _no_trusted_callables)

Create a call interceptor that blocks unsafe callables.

Whenpolicy.allow_callingis None, non-blocked callable types are permitted (attribute-level checks are still enforced). When set to a frozenset of type names, only instances of those types may be called. Blocked types (function, type, code) are always denied unless the callable is an application-registered environment global or a function compiled inside this template namespace. The explicitallow_callingpolicy is still applied to those trusted callables when configured.

Parameters
Name Type Description
policy SandboxPolicy
namespace dict[str, Any] | None Default:None
sandbox_marker object Default:_DEFAULT_SANDBOX_MARKER
trusted_callables Callable[[], Iterable[object]] Default:_no_trusted_callables
_make_sandboxed_optional_call 1
Preserve optional-call short circuiting while enforcing call policy.
def _make_sandboxed_optional_call(sandboxed_call: Callable[..., Any])
Parameters
Name Type Description
sandboxed_call Callable[..., Any]
patch_template_namespace 4 None
Patch a template namespace dict to enforce sandbox restrictions. Called by Tem…
def patch_template_namespace(namespace: dict[str, Any], policy: SandboxPolicy, *, sandbox_marker: object = _DEFAULT_SANDBOX_MARKER, trusted_callables: Callable[[], Iterable[object]] = _no_trusted_callables) -> None

Patch a template namespace dict to enforce sandbox restrictions.

Called by Template.init when the environment is a SandboxedEnvironment. Replaces unsafe functions with sandboxed versions.

Parameters
Name Type Description
namespace dict[str, Any]
policy SandboxPolicy
sandbox_marker object Default:_DEFAULT_SANDBOX_MARKER
trusted_callables Callable[[], Iterable[object]] Default:_no_trusted_callables