Safe Builtins

When executing untrusted Python code, we need to make sure that we only give the code access to safe, basic or proxied objects. This included the builtin objects provided to Python code through a special __builtins__ module in globals. The builtins module provides a suitable module object:

>>> from zope.security.untrustedpython.builtins import SafeBuiltins
>>> d = {'__builtins__': SafeBuiltins}
>>> exec 'x = str(1)' in d
>>> d['x']
'1'

The object is immutable:

>>> SafeBuiltins.foo = 1
Traceback (most recent call last):
...
AttributeError: foo
>>> del SafeBuiltins['getattr']
Traceback (most recent call last):
...
TypeError: object does not support item deletion

Exception raised: ... TypeError: object does not support item deletion

(Note that you can mutate it through its __dict__ attribute,
however, when combined with the untrusted code compiler, getting the __dict__ attribute will return a proxied object that will prevent mutation.)

It contains items with keys that are all strings and values that are either proxied or are basic types:

>>> from zope.security.proxy import Proxy
>>> for key, value in SafeBuiltins.__dict__.items():
...     if not isinstance(key, str):
...         raise TypeError(key)
...     if value is not None and not isinstance(value, (Proxy, int, str)):
...         raise TypeError(value, key)

It doesn't contain unsafe items, such as eval, globals, etc:

>>> SafeBuiltins.eval
Traceback (most recent call last):
...
AttributeError: 'ImmutableModule' object has no attribute 'eval'
>>> SafeBuiltins.globals
Traceback (most recent call last):
...
AttributeError: 'ImmutableModule' object has no attribute 'globals'

The safe builtins also contains a custom __import__ function.

>>> imp = SafeBuiltins.__import__

As with regular import, it only returns the top-level package if no fromlist is specified:

>>> import zope.security
>>> imp('zope.security') == zope
True
>>> imp('zope.security', {}, {}, ['*']) == zope.security
True

Note that the values returned are proxied:

>>> type(imp('zope.security')) is Proxy
True

This means that, having imported a module, you will only be able to access attributes for which you are authorized.

Unlike regular __import__, you can nly import modules that have been previously imported. This is to prevent unauthorized execution of module-initialization code:

>>> security = zope.security
>>> import sys
>>> del sys.modules['zope.security']
>>> imp('zope.security')
Traceback (most recent call last):
...
ImportError: zope.security
>>> sys.modules['zope.security'] = security

Package-relative imports are supported (for now):

>>> imp('security', {'__name__': 'zope', '__path__': []}) == security
True
>>> imp('security', {'__name__': 'zope.foo'}) == zope.security
True
>>> imp('security.untrustedpython', {'__name__': 'zope.foo'}) == security
True
>>> from zope.security import untrustedpython
>>> imp('security.untrustedpython', {'__name__': 'zope.foo'}, {}, ['*']
...     ) == untrustedpython
True