Usage of RestrictedPython

Basic usage

The general workflow to execute Python code that is loaded within a Python program is:

source_code = """
def do_something():
    pass
"""

byte_code = compile(source_code, filename='<inline code>', mode='exec')
exec(byte_code)
do_something()

With RestrictedPython that workflow should be as straight forward as possible:

from RestrictedPython import compile_restricted

source_code = """
def do_something():
    pass
"""

byte_code = compile_restricted(source_code,
                               filename='<inline code>',
                               mode='exec')
exec(byte_code)
do_something()

You might also use the replacement import:

from RestrictedPython import compile_restricted as compile

compile_restricted uses a predefined policy that checks and modify the source code and checks against a restricted subset of the Python language. The compiled source code is still executed against the full available set of library modules and methods.

The Python exec() takes three parameters:

  • code which is the compiled byte code
  • globals which is global dictionary
  • locals which is the local dictionary

By limiting the entries in the globals and locals dictionaries you restrict the access to the available library modules and methods.

Providing defined dictionaries for exec() should be used in context of RestrictedPython.

byte_code = <code>
exec(byte_code, { ... }, { ... })

Typically there is a defined set of allowed modules, methods and constants used in that context. RestrictedPython provides three predefined built-ins for that (see Predefined builtins for details):

  • safe_builtins
  • limited_builtins
  • utility_builtins

So you normally end up using:

from RestrictedPython import compile_restricted

from RestrictedPython import safe_builtins
from RestrictedPython import limited_builtins
from RestrictedPython import utility_builtins

source_code = """
def do_something():
    pass
"""

try:
    byte_code = compile_restricted(source_code,
                                   filename='<inline code>',
                                   mode='exec')

    exec(byte_code, safe_builtins, None)
except SyntaxError as e:
    pass

One common advanced usage would be to define an own restricted builtin dictionary.

Necessary setup

RestrictedPython requires some predefined names in globals in order to work properly.

To use classes in Python 3
__metaclass__ must be set. Set it to type to use no custom metaclass.
To use for statements and comprehensions
_iter_unpack_sequence_ must point to RestrictedPython.Guards.guarded_iter_unpack_sequence().

The usage of RestrictedPython in AccessControl.ZopeGuards can serve as example.

Usage in frameworks and Zope

One major issue with using compile_restricted directly in a framework is, that you have to use try-except statements to handle problems and it might be a bit harder to provide useful information to the user. RestrictedPython provides four specialized compile_restricted methods:

  • compile_restricted_exec
  • compile_restricted_eval
  • compile_restricted_single
  • compile_restricted_function

Those four methods return a named tuple (CompileResult) with four elements:

  • code <code> object or None if errors is not empty
  • errors a tuple with error messages
  • warnings a list with warnings
  • used_names a set / dictionary with collected used names of library calls

Those three information “lists” could be used to provide the user with informations about the compiled source code.

Typical uses cases for the four specialized methods:

  • compile_restricted_exec –> Python Modules or Scripts that should be used or called by the framework itself or from user calls
  • compile_restricted_eval –> Templates
  • compile_restricted_single
  • compile_restricted_function

Modifying the builtins is straight forward, it is just a dictionary containing access pointers to available library elements. Modification is normally removing elements from existing builtins or adding allowed elements by copying from globals.

For frameworks it could possibly also be useful to change handling of specific Python language elements. For that use case RestrictedPython provides the possibility to pass an own policy.

A policy is basically a special NodeTransformer that could be instantiated with three params for errors, warnings and used_names, it should be a subclass of RestrictedPython.RestrictingNodeTransformer.

from RestrictedPython import compile_restricted
from RestrictedPython import RestrictingNodeTransformer

class OwnRestrictingNodeTransformer(RestrictingNodeTransformer):
    pass

policy_instance = OwnRestrictingNodeTransformer(errors=[],
                                                warnings=[],
                                                used_names=[])

All compile_restricted* methods do have a optional parameter policy, where a specific policy could be provided.

source_code = """
def do_something():
    pass
"""

policy = OwnRestrictingNodeTransformer

byte_code = compile_restricted(source_code,
                               filename='<inline code>',
                               mode='exec',
                               policy=policy # Policy Class
                               )
exec(byte_code, globals(), None)

One special case “unrestricted RestrictedPython” (defined to unblock ports of Zope Packages to Python 3) is to actually use RestrictedPython in an unrestricted mode, by providing a Null-Policy (aka None). That special case would be written as:

from RestrictedPython import compile_restricted

source_code = """
def do_something():
    pass
"""

byte_code = compile_restricted(source_code,
                               filename='<inline code>',
                               mode='exec',
                               policy=None # Null-Policy -> unrestricted
                               )
exec(byte_code, globals(), None)

Policies & builtins

Todo

Should be described in detail. Especially the difference between builtins and a policy which is a NodeTransformer.

RestrictedPython provides a way to define Policies, by redefining restricted versions of print, getattr, setattr, import, etc.. As shortcuts it offers three stripped down versions of Pythons __builtins__:

Predefined builtins

Todo

Describe more in details

  • safe_builtins a safe set of builtin modules and functions,
  • limited_builtins which provides restricted sequence types,
  • utility_builtins which provides access for standard modules math, random, string and for sets.

Guards

Todo

Describe Guards and predefined guard methods in details

RestrictedPython predefines several guarded access and manipulation methods:

  • guarded_setattr
  • guarded_delattr
  • guarded_iter_unpack_sequence
  • guarded_unpack_sequence

Those and additional methods rely on a helper construct full_write_guard, which is intended to help implement immutable and semi mutable objects and attributes.

Todo

Describe full_write_guard more in detail and how it works.

API overview

RestrictedPython has tree major scopes:

  1. compile_restricted methods:
RestrictedPython.compile_restricted(source, filename, mode, flags, dont_inherit, policy)

Compiles source code into interpretable byte code.

Parameters:
  • source (str or unicode text) – (required). The source code that should be compiled
  • filename (str or unicode text) – (optional).
  • mode (str or unicode text) – (optional).
  • flags (int) – (optional).
  • dont_inherit (int) – (optional).
  • policy (RestrictingNodeTransformer class) – (optional).
Returns:

Byte Code

RestrictedPython.compile_restricted_exec(source, filename, flags, dont_inherit, policy)

Compiles source code into interpretable byte code.

Parameters:
  • source (str or unicode text) – (required). The source code that should be compiled
  • filename (str or unicode text) – (optional).
  • flags (int) – (optional).
  • dont_inherit (int) – (optional).
  • policy (RestrictingNodeTransformer class) – (optional).
Returns:

CompileResult (a namedtuple with code, errors, warnings, used_names)

RestrictedPython.compile_restricted_eval(source, filename, flags, dont_inherit, policy)

Compiles source code into interpretable byte code.

Parameters:
  • source (str or unicode text) – (required). The source code that should be compiled
  • filename (str or unicode text) – (optional).
  • flags (int) – (optional).
  • dont_inherit (int) – (optional).
  • policy (RestrictingNodeTransformer class) – (optional).
Returns:

CompileResult (a namedtuple with code, errors, warnings, used_names)

RestrictedPython.compile_restricted_single(source, filename, flags, dont_inherit, policy)

Compiles source code into interpretable byte code.

Parameters:
  • source (str or unicode text) – (required). The source code that should be compiled
  • filename (str or unicode text) – (optional).
  • flags (int) – (optional).
  • dont_inherit (int) – (optional).
  • policy (RestrictingNodeTransformer class) – (optional).
Returns:

CompileResult (a namedtuple with code, errors, warnings, used_names)

RestrictedPython.compile_restricted_function(p, body, name, filename, globalize=None)

Compiles source code into interpretable byte code.

Parameters:
  • p – (required).
  • body – (required).
  • name (str or unicode text) – (required).
  • filename (str or unicode text) – (required).
  • globalize – (optional).
Returns:

byte code

  1. restricted builtins
  • safe_builtins
  • limited_builtins
  • utility_builtins
  1. helper modules
  • PrintCollector