Untrusted Python interpreter

The interpreter module provides very basic Python interpreter support. It combined untrusted code compilation with safe builtins and an exec-like API. The exec_src function can be used to execute Python source:

>>> from zope.security.untrustedpython.interpreter import exec_src
>>> d = {}
>>> exec_src("x=1", d)
>>> d['x']
1
>>> exec_src("x=getattr", d)

Note that the safe builtins dictionary is inserted into the dictionary:

>>> from zope.security.untrustedpython.builtins import SafeBuiltins
>>> d['__builtins__'] == SafeBuiltins
True

All of the non-basic items in the safe builtins are proxied:

>>> exec_src('str=str', d)
>>> from zope.security.proxy import Proxy
>>> type(d['str']) is Proxy
True

Note that, while you can get to the safe __builtins__'s dictionary, you can't use the dictionary to mutate it:

>>> from zope.security.interfaces import ForbiddenAttribute
>>> try: exec_src('__builtins__.__dict__["x"] = 1', d)
... except ForbiddenAttribute: print 'Forbidden!'
Forbidden!
>>> try: exec_src('del __builtins__.__dict__["str"]', d)
... except ForbiddenAttribute: print 'Forbidden!'
Forbidden!
>>> try: exec_src('__builtins__.__dict__.update({"x": 1})', d)
... except ForbiddenAttribute: print 'Forbidden!'
Forbidden!

Because the untrusted code compiler is used, you can't use exec, raise, or try/except statements:

>>> exec_src("exec 'x=1'", d)
Traceback (most recent call last):
...
SyntaxError: Line 1: exec statements are not supported

Any attribute-access results will be proxied:

>>> exec_src("data = {}\nupdate = data.update\nupdate({'x': 'y'})", d)
>>> type(d['update']) is Proxy
True

In this case, we were able to get to and use the update method because the data dictionary itself was created by the untrusted code and was, thus, unproxied.

You can compile code yourself and call exec_code instead:

>>> from zope.security.untrustedpython.rcompile import compile
>>> code = compile('x=2', '<mycode>', 'exec')
>>> d = {}
>>> from zope.security.untrustedpython.interpreter import exec_code
>>> exec_code(code, d)
>>> d['x']
2

This is useful if you are going to be executing the same expression many times, as you can avoid the cost of repeated comilation.

Compiled Programs

A slightly higher-level interface is provided by compiled programs. These make it easier to safetly safe the results of compilation:

>>> from zope.security.untrustedpython.interpreter import CompiledProgram
>>> p = CompiledProgram('x=2')
>>> d = {}
>>> p.exec_(d)
>>> d['x']
2

When you execute a compiled program, you can supply an object with a write method to get print output:

>>> p = CompiledProgram('print "Hello world!"')
>>> import cStringIO
>>> f = cStringIO.StringIO()
>>> p.exec_({}, output=f)
>>> f.getvalue()
'Hello world!\n'

Compiled Expressions

You can also precompile expressions:

>>> from zope.security.untrustedpython.interpreter import CompiledExpression
>>> p = CompiledExpression('x*2')
>>> p.eval({'x': 2})
4