Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jinja2/sandbox.py: 31%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""A sandbox layer that ensures unsafe operations cannot be performed.
2Useful when the template itself comes from an untrusted source.
3"""
5import operator
6import types
7import typing as t
8from _string import formatter_field_name_split # type: ignore
9from collections import abc
10from collections import deque
11from functools import update_wrapper
12from string import Formatter
14from markupsafe import EscapeFormatter
15from markupsafe import Markup
17from .environment import Environment
18from .exceptions import SecurityError
19from .runtime import Context
20from .runtime import Undefined
22F = t.TypeVar("F", bound=t.Callable[..., t.Any])
24#: maximum number of items a range may produce
25MAX_RANGE = 100000
27#: Unsafe function attributes.
28UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
30#: Unsafe method attributes. Function attributes are unsafe for methods too.
31UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
33#: unsafe generator attributes.
34UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
36#: unsafe attributes on coroutines
37UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
39#: unsafe attributes on async generators
40UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
42_mutable_spec: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = (
43 (
44 abc.MutableSet,
45 frozenset(
46 [
47 "add",
48 "clear",
49 "difference_update",
50 "discard",
51 "pop",
52 "remove",
53 "symmetric_difference_update",
54 "update",
55 ]
56 ),
57 ),
58 (
59 abc.MutableMapping,
60 frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
61 ),
62 (
63 abc.MutableSequence,
64 frozenset(
65 ["append", "clear", "pop", "reverse", "insert", "sort", "extend", "remove"]
66 ),
67 ),
68 (
69 deque,
70 frozenset(
71 [
72 "append",
73 "appendleft",
74 "clear",
75 "extend",
76 "extendleft",
77 "pop",
78 "popleft",
79 "remove",
80 "rotate",
81 ]
82 ),
83 ),
84)
87def safe_range(*args: int) -> range:
88 """A range that can't generate ranges with a length of more than
89 MAX_RANGE items.
90 """
91 rng = range(*args)
93 if len(rng) > MAX_RANGE:
94 raise OverflowError(
95 "Range too big. The sandbox blocks ranges larger than"
96 f" MAX_RANGE ({MAX_RANGE})."
97 )
99 return rng
102def unsafe(f: F) -> F:
103 """Marks a function or method as unsafe.
105 .. code-block: python
107 @unsafe
108 def delete(self):
109 pass
110 """
111 f.unsafe_callable = True # type: ignore
112 return f
115def is_internal_attribute(obj: t.Any, attr: str) -> bool:
116 """Test if the attribute given is an internal python attribute. For
117 example this function returns `True` for the `func_code` attribute of
118 python objects. This is useful if the environment method
119 :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
121 >>> from jinja2.sandbox import is_internal_attribute
122 >>> is_internal_attribute(str, "mro")
123 True
124 >>> is_internal_attribute(str, "upper")
125 False
126 """
127 if isinstance(obj, types.FunctionType):
128 if attr in UNSAFE_FUNCTION_ATTRIBUTES:
129 return True
130 elif isinstance(obj, types.MethodType):
131 if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES:
132 return True
133 elif isinstance(obj, type):
134 if attr == "mro":
135 return True
136 elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
137 return True
138 elif isinstance(obj, types.GeneratorType):
139 if attr in UNSAFE_GENERATOR_ATTRIBUTES:
140 return True
141 elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType):
142 if attr in UNSAFE_COROUTINE_ATTRIBUTES:
143 return True
144 elif hasattr(types, "AsyncGeneratorType") and isinstance(
145 obj, types.AsyncGeneratorType
146 ):
147 if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
148 return True
149 return attr.startswith("__")
152def modifies_known_mutable(obj: t.Any, attr: str) -> bool:
153 """This function checks if an attribute on a builtin mutable object
154 (list, dict, set or deque) or the corresponding ABCs would modify it
155 if called.
157 >>> modifies_known_mutable({}, "clear")
158 True
159 >>> modifies_known_mutable({}, "keys")
160 False
161 >>> modifies_known_mutable([], "append")
162 True
163 >>> modifies_known_mutable([], "index")
164 False
166 If called with an unsupported object, ``False`` is returned.
168 >>> modifies_known_mutable("foo", "upper")
169 False
170 """
171 for typespec, unsafe in _mutable_spec:
172 if isinstance(obj, typespec):
173 return attr in unsafe
174 return False
177class SandboxedEnvironment(Environment):
178 """The sandboxed environment. It works like the regular environment but
179 tells the compiler to generate sandboxed code. Additionally subclasses of
180 this environment may override the methods that tell the runtime what
181 attributes or functions are safe to access.
183 If the template tries to access insecure code a :exc:`SecurityError` is
184 raised. However also other exceptions may occur during the rendering so
185 the caller has to ensure that all exceptions are caught.
186 """
188 sandboxed = True
190 #: default callback table for the binary operators. A copy of this is
191 #: available on each instance of a sandboxed environment as
192 #: :attr:`binop_table`
193 default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
194 "+": operator.add,
195 "-": operator.sub,
196 "*": operator.mul,
197 "/": operator.truediv,
198 "//": operator.floordiv,
199 "**": operator.pow,
200 "%": operator.mod,
201 }
203 #: default callback table for the unary operators. A copy of this is
204 #: available on each instance of a sandboxed environment as
205 #: :attr:`unop_table`
206 default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
207 "+": operator.pos,
208 "-": operator.neg,
209 }
211 #: a set of binary operators that should be intercepted. Each operator
212 #: that is added to this set (empty by default) is delegated to the
213 #: :meth:`call_binop` method that will perform the operator. The default
214 #: operator callback is specified by :attr:`binop_table`.
215 #:
216 #: The following binary operators are interceptable:
217 #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
218 #:
219 #: The default operation form the operator table corresponds to the
220 #: builtin function. Intercepted calls are always slower than the native
221 #: operator call, so make sure only to intercept the ones you are
222 #: interested in.
223 #:
224 #: .. versionadded:: 2.6
225 intercepted_binops: t.FrozenSet[str] = frozenset()
227 #: a set of unary operators that should be intercepted. Each operator
228 #: that is added to this set (empty by default) is delegated to the
229 #: :meth:`call_unop` method that will perform the operator. The default
230 #: operator callback is specified by :attr:`unop_table`.
231 #:
232 #: The following unary operators are interceptable: ``+``, ``-``
233 #:
234 #: The default operation form the operator table corresponds to the
235 #: builtin function. Intercepted calls are always slower than the native
236 #: operator call, so make sure only to intercept the ones you are
237 #: interested in.
238 #:
239 #: .. versionadded:: 2.6
240 intercepted_unops: t.FrozenSet[str] = frozenset()
242 def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
243 super().__init__(*args, **kwargs)
244 self.globals["range"] = safe_range
245 self.binop_table = self.default_binop_table.copy()
246 self.unop_table = self.default_unop_table.copy()
248 def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
249 """The sandboxed environment will call this method to check if the
250 attribute of an object is safe to access. Per default all attributes
251 starting with an underscore are considered private as well as the
252 special attributes of internal python objects as returned by the
253 :func:`is_internal_attribute` function.
254 """
255 return not (attr.startswith("_") or is_internal_attribute(obj, attr))
257 def is_safe_callable(self, obj: t.Any) -> bool:
258 """Check if an object is safely callable. By default callables
259 are considered safe unless decorated with :func:`unsafe`.
261 This also recognizes the Django convention of setting
262 ``func.alters_data = True``.
263 """
264 return not (
265 getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
266 )
268 def call_binop(
269 self, context: Context, operator: str, left: t.Any, right: t.Any
270 ) -> t.Any:
271 """For intercepted binary operator calls (:meth:`intercepted_binops`)
272 this function is executed instead of the builtin operator. This can
273 be used to fine tune the behavior of certain operators.
275 .. versionadded:: 2.6
276 """
277 return self.binop_table[operator](left, right)
279 def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any:
280 """For intercepted unary operator calls (:meth:`intercepted_unops`)
281 this function is executed instead of the builtin operator. This can
282 be used to fine tune the behavior of certain operators.
284 .. versionadded:: 2.6
285 """
286 return self.unop_table[operator](arg)
288 def getitem(
289 self, obj: t.Any, argument: t.Union[str, t.Any]
290 ) -> t.Union[t.Any, Undefined]:
291 """Subscribe an object from sandboxed code."""
292 try:
293 return obj[argument]
294 except (TypeError, LookupError):
295 if isinstance(argument, str):
296 try:
297 attr = str(argument)
298 except Exception:
299 pass
300 else:
301 try:
302 value = getattr(obj, attr)
303 except AttributeError:
304 pass
305 else:
306 fmt = self.wrap_str_format(value)
307 if fmt is not None:
308 return fmt
309 if self.is_safe_attribute(obj, argument, value):
310 return value
311 return self.unsafe_undefined(obj, argument)
312 return self.undefined(obj=obj, name=argument)
314 def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
315 """Subscribe an object from sandboxed code and prefer the
316 attribute. The attribute passed *must* be a bytestring.
317 """
318 try:
319 value = getattr(obj, attribute)
320 except AttributeError:
321 try:
322 return obj[attribute]
323 except (TypeError, LookupError):
324 pass
325 else:
326 fmt = self.wrap_str_format(value)
327 if fmt is not None:
328 return fmt
329 if self.is_safe_attribute(obj, attribute, value):
330 return value
331 return self.unsafe_undefined(obj, attribute)
332 return self.undefined(obj=obj, name=attribute)
334 def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined:
335 """Return an undefined object for unsafe attributes."""
336 return self.undefined(
337 f"access to attribute {attribute!r} of"
338 f" {type(obj).__name__!r} object is unsafe.",
339 name=attribute,
340 obj=obj,
341 exc=SecurityError,
342 )
344 def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]:
345 """If the given value is a ``str.format`` or ``str.format_map`` method,
346 return a new function than handles sandboxing. This is done at access
347 rather than in :meth:`call`, so that calls made without ``call`` are
348 also sandboxed.
349 """
350 if not isinstance(
351 value, (types.MethodType, types.BuiltinMethodType)
352 ) or value.__name__ not in ("format", "format_map"):
353 return None
355 f_self: t.Any = value.__self__
357 if not isinstance(f_self, str):
358 return None
360 str_type: t.Type[str] = type(f_self)
361 is_format_map = value.__name__ == "format_map"
362 formatter: SandboxedFormatter
364 if isinstance(f_self, Markup):
365 formatter = SandboxedEscapeFormatter(self, escape=f_self.escape)
366 else:
367 formatter = SandboxedFormatter(self)
369 vformat = formatter.vformat
371 def wrapper(*args: t.Any, **kwargs: t.Any) -> str:
372 if is_format_map:
373 if kwargs:
374 raise TypeError("format_map() takes no keyword arguments")
376 if len(args) != 1:
377 raise TypeError(
378 f"format_map() takes exactly one argument ({len(args)} given)"
379 )
381 kwargs = args[0]
382 args = ()
384 return str_type(vformat(f_self, args, kwargs))
386 return update_wrapper(wrapper, value)
388 def call(
389 __self, # noqa: B902
390 __context: Context,
391 __obj: t.Any,
392 *args: t.Any,
393 **kwargs: t.Any,
394 ) -> t.Any:
395 """Call an object from sandboxed code."""
397 # the double prefixes are to avoid double keyword argument
398 # errors when proxying the call.
399 if not __self.is_safe_callable(__obj):
400 raise SecurityError(f"{__obj!r} is not safely callable")
401 return __context.call(__obj, *args, **kwargs)
404class ImmutableSandboxedEnvironment(SandboxedEnvironment):
405 """Works exactly like the regular `SandboxedEnvironment` but does not
406 permit modifications on the builtin mutable objects `list`, `set`, and
407 `dict` by using the :func:`modifies_known_mutable` function.
408 """
410 def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
411 if not super().is_safe_attribute(obj, attr, value):
412 return False
414 return not modifies_known_mutable(obj, attr)
417class SandboxedFormatter(Formatter):
418 def __init__(self, env: Environment, **kwargs: t.Any) -> None:
419 self._env = env
420 super().__init__(**kwargs)
422 def get_field(
423 self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
424 ) -> t.Tuple[t.Any, str]:
425 first, rest = formatter_field_name_split(field_name)
426 obj = self.get_value(first, args, kwargs)
427 for is_attr, i in rest:
428 if is_attr:
429 obj = self._env.getattr(obj, i)
430 else:
431 obj = self._env.getitem(obj, i)
432 return obj, first
435class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter):
436 pass