restricted execution

Evoque supports python expressions only. This means that python statements are already naturally restricted as when evaluated these will always raise a SyntaxError. The problem of restricting execution is thus reduced to the problem of making the eval python built-in safe. This is by no means an easy task, but at least with its more limited scope it is simpler than attempting to generically restrict or sandbox the python interpreter.

Running in restricted mode will (supposedly) make it impossible to manipulate tangible resources, such as files and sockets, from within a template. Protection of intangible resources, such as memory and CPU usage, is not (yet) provided (see below).

restricted: bool = False
This is the one domain init parameter that when set to True will initialize the domain to run in restricted execution mode. Setting this to True has therefore the following consequences:

1. Sets a dummy __builtins__
Sets a dummy __builtins__ empty dict on the domain-wide globals dict used by eval.

2. No builtins that are deemed unsafe
For every builtin that is not deemed unsafe, will add a top-level entry to the domain-wide globals dict used by eval. Python (2.4, 2.5, 2.6, 3.0) __builtins__ currently considered unsafe are (defined in the list Domain.DISALLOW_BUILTINS):

DISALLOW_BUILTINS = ["_", "__debug__", "__doc__", "__import__", "__name__",
    "buffer", "callable", "classmethod", "coerce", "compile", "delattr", "dir",
    "eval", "execfile", "exit", "file", "getattr", "globals", "hasattr", "id",
    "input", "isinstance", "issubclass", "locals", "object", "open", "quit", 
    "raw_input", "reload", "setattr", "staticmethod", "super", "type", "vars"]

In addition to the above, all subclasses of BaseException (or, in Python 2.4, of Exception) are considered potentially unsafe and so are not made available as entries on the globals dict.

This may seem overly restrictive, but then templating should not require more than a small subset of python's possibilities, for convenience of doing simple progamming tasks e.g. enumerating over a list.

3. Runtime scan of all expressions
At runtime, all string expressions to be evaluated, necessarily via restrictedEvaluator[expression], are first scanned for any disallowed attribute lookups, and if a string expression matches, a LookupError will be raised. The restricted_scan pattern will match any attempt to access any attribute that starts with one of the following character sequences:

restricted_scan = re.compile(r"|\.\s*".join([
            "__", "func_", "f_", "im_", "tb_", "gi_", "throw"]), re.DOTALL)

Note that expression-building trickery will not achieve anything, as expressions are never evaluated twice i.e. an expression may build a string but such a string cannot in anyway be evaluated. To illustrate, consider an expression such as ${legit_obj.__subclasses__()}. This will fail because it attempts an attribute lookup that starts with "__". Attempting to bypass this by doing something like ${"legit_obj." + "_"*2 + "subclasses" + "_"*2 + "()"} will simply return the string "legit_obj.__subclasses__()" that cannot be re-rendered.

4. No re-evaluation of expressions
Evoque takes every precaution possible to make it impossible to re-evaluate rendered results in any way. For example, Evoque templates categorically cannot arbitrarily set a variable, or initialize a new string-based template. Furthermore, under restricted mode, builtins such as eval or getattr are of course not available.

Is this enough?

Depending on paranoia level, probably not. Besides more testing to validate that the above effectively does not allow the template programmer to access and manipulate any tangible resources, it should still be possible to bring down the interpreter via DOS maliciousness, e.g. evaluating an expression for a very large multiplication to consume all available memory, or to take a very long time to finish.

Brett Cannon and Eric Wohlstadter, from UBC in Vancouver, have done some thorough research into the feasibility of more ambitiously applying a similar but more generalized approach to fully restrict the python interpreter i.e. all possible expressions and statement constructs -- they have summarized their work in this inspirational paper:
Controlling Access to Resources Within The Python Interpreter

Nonetheless, further runtime analysis of expressions will be required to protect against intangible resources such as memory and CPU usage, and how best to add such protection is future work that is still to be done. Thus, for the paranoid, restricted execution mode should for now best be considered experimental.