Support for Restricted Python Code

This package provides a way to compile untrusted Python code so that it can be executed safely.

This form of restricted Python assumes that security proxies will be used to protect assets. Given this, the only thing that actually needs to be done differently by the generated code is to:

No other special treatment is needed to support safe expression evaluation.

The implementation makes use of the RestrictedPython package, originally written for Zope 2. There is a new AST re-writer in zope.security.untrustedpython.rcompile which performs the tree-transformation, and a top-level compile() function in zope.security.untrustedpython.rcompile; the later is what client applications are expected to use.

The signature of the compile() function is very similar to that of Python's built-in compile() function:

compile(source, filename, mode)

Using it is equally simple:

>>> from zope.security.untrustedpython.rcompile import compile

>>> code = compile("21 * 2", "<string>", "eval")
>>> eval(code)
42

What's interesting about the restricted code is that all attribute lookups go through the getattr() function. This is generally provided as a built-in function in the restricted environment:

>>> def mygetattr(object, name, default="Yahoo!"):
...     marker = []
...     print "Looking up", name
...     if getattr(object, name, marker) is marker:
...         return default
...     else:
...         return "Yeehaw!"

>>> import __builtin__
>>> builtins = __builtin__.__dict__.copy()
>>> builtins["getattr"] = mygetattr

>>> def reval(source):
...     code = compile(source, "README.txt", "eval")
...     globals = {"__builtins__": builtins}
...     return eval(code, globals, {})

>>> reval("(42).__class__")
Looking up __class__
'Yeehaw!'
>>> reval("(42).not_really_there")
Looking up not_really_there
'Yahoo!'
>>> reval("(42).foo.not_really_there")
Looking up foo
Looking up not_really_there
'Yahoo!'

This allows a getattr() to be used that ensures the result of evaluation is a security proxy.

To compile code with statements, use exec or single:

>>> exec compile("x = 1", "<string>", "exec")
>>> x
1

Trying to compile exec, raise or try/except sattements gives syntax errors:

>>> compile("exec 'x = 2'", "<string>", "exec")
Traceback (most recent call last):
...
SyntaxError: Line 1: exec statements are not supported
>>> compile("raise KeyError('x')", "<string>", "exec")
Traceback (most recent call last):
...
SyntaxError: Line 1: raise statements are not supported
>>> compile("try: pass\nexcept: pass", "<string>", "exec")
Traceback (most recent call last):
...
SyntaxError: Line 1: try/except statements are not supported

Printing to an explicit writable is allowed:

>>> import StringIO
>>> f = StringIO.StringIO()
>>> code = compile("print >> f, 'hi',\nprint >> f, 'world'", '', 'exec')
>>> exec code in {'f': f}
>>> f.getvalue()
'hi world\n'

But if no output is specified, then output will be send to untrusted_output:

>>> code = compile("print 'hi',\nprint 'world'", '', 'exec')
>>> exec code in {}
Traceback (most recent call last):
...
NameError: name 'untrusted_output' is not defined
>>> f = StringIO.StringIO()
>>> exec code in {'untrusted_output': f}