nodejs/node · error · SecurityError
%r is not safely callable
Error message
%r is not safely callable
What it means
Raised by SandboxedEnvironment.call when the object being invoked from sandboxed template code is not marked as safely callable. is_safe_callable returns True only for callables explicitly approved: marked with @safe or possessing the unsafe_callable attribute unset (default safe), and explicitly excluded when decorated with @unsafe. The SecurityError prevents sandboxed templates from invoking arbitrary Python functions.
Source
Thrown at tools/inspector_protocol/jinja2/sandbox.py:426
"""
if isinstance(s, Markup):
formatter = SandboxedEscapeFormatter(self, s.escape)
else:
formatter = SandboxedFormatter(self)
kwargs = _MagicFormatMapping(args, kwargs)
rv = formatter.vformat(s, args, kwargs)
return type(s)(rv)
def call(__self, __context, __obj, *args, **kwargs):
"""Call an object from sandboxed code."""
fmt = inspect_format_method(__obj)
if fmt is not None:
return __self.format_string(fmt, args, kwargs)
# the double prefixes are to avoid double keyword argument
# errors when proxying the call.
if not __self.is_safe_callable(__obj):
raise SecurityError('%r is not safely callable' % (__obj,))
return __context.call(__obj, *args, **kwargs)
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
"""Works exactly like the regular `SandboxedEnvironment` but does not
permit modifications on the builtin mutable objects `list`, `set`, and
`dict` by using the :func:`modifies_known_mutable` function.
"""
def is_safe_attribute(self, obj, attr, value):
if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
return False
return not modifies_known_mutable(obj, attr)
# This really is not a public API apparenlty.
try:
from _string import formatter_field_name_splitView on GitHub (pinned to 1b2de5e052)
Solutions
- Decorate the genuinely safe callable with @jinja2.sandbox.safe so is_safe_callable returns True.
- Do not pass the callable into the context; instead expose a vetted wrapper function that performs the operation in Python.
- Refactor the template to call only attributes/values, not arbitrary methods; precompute results in Python.
- Audit context objects: replace live ORM/manager objects with plain dicts or data-transfer objects.
Example fix
// before
def render(template_str, ctx):
return SandboxedEnvironment().from_string(template_str).render(**ctx)
render('{{ acct.delete() }}', {'acct': Account(...)})
// after
from jinja2.sandbox import safe
@safe
def delete_account(acct):
acct.delete()
return 'deleted'
render('{{ delete_account(acct) }}', {'acct': acct, 'delete_account': delete_account}) Defensive patterns
Strategy: type-guard
Validate before calling
from jinja2.sandbox import SandboxedEnvironment, unsafe
# whitelist: build the context from a dataclass/dict, never from live model objects
ctx = {'user': {'name': acct.name, 'email': acct.email}} # plain dict only
env = SandboxedEnvironment()
env.from_string(tpl).render(**ctx) Type guard
from jinja2.sandbox import safe
from functools import wraps
def is_safe_callable(obj) -> bool:
return callable(obj) and not getattr(obj, 'unsafe_callable', False)
# explicit allow-list decorator
@safe
def approved(fn):
return fn Try / catch
from jinja2.exceptions import SecurityError
try:
env.from_string(tpl).render(**ctx)
except SecurityError as e:
log.warning('sandbox rejected callable: %s', e)
html = render_safe_fallback() Prevention
- Pass plain dicts / DTOs into sandboxed templates, never ORM models or framework objects.
- Mark callables @jinja2.sandbox.safe only after a deliberate review.
- Run user-supplied templates under ImmutableSandboxedEnvironment in addition to the basic sandbox.
When it happens
Trigger: Template calls {{ user.delete_account() }} or {{ os.system('rm -rf') }} where the callable was passed into the context but is not approved. Passing a class instance method marked @unsafe into the template context and invoking it. Calling a callable that the sandbox does not recognize as formatted/known-safe.
Common situations: Leaking ORM model methods or framework helpers into the template context. Using SandboxedEnvironment for user-uploaded templates and accidentally exposing privileged callables. Marking too many things @unsafe during a security review and breaking legitimate calls.
Related errors
- {template}
- range too big, maximum size for range is %d
- The ${key} option is protected, and cannot be retrieved in t
- npm profile set password Do not include your current or new
- Invalid URL: ${url}
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/ab140f720243646f.
Report an issue: GitHub.