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
  






