On Mon, 10 Mar 2003, Jim Fulton wrote:
Maybe every Python object should have a flag which can be set to prevent introspection -- like the current restricted execution mechanism, but on a per-object basis. Then any object could be used as a capability.
Yes, but not a very useful one. For example, given a file, you often want to create a "file read" capability which is an object that allows reading the file but not writing the file. Just preventing introspection isn't enough.
All right. Let me provide an example; maybe this can help ground the discussion a bit. We seem to be doing a lot of dancing around the issue of what a capability is.
In my view, it's a red herring to discuss whether or not a particular object "is a capability" or not. It's like asking whether something is an "object". Capabilities are a discipline under which objects are used -- it's better to think of them as a technique or a style of programming.
What is at issue here (IMHO) is "how might Python change to facilitate this style of programming?"
(The analogy with object-oriented programming holds here also. Even if Python didn't have a "class" keyword, you could still program in an object-oriented style. In fact, the C implementation of Python is clearly object-oriented, even though C has no features specifically designed for OOP. But adding "class" made it a lot easier to do a particular style of object-oriented programming in Python. Unfortunately, the particular style encouraged by Python's "class" keyword doesn't work so well for capability-style programming, because all instance state is public. But Python's "class" is not the only way to do object-oriented programming -- see below.)
Okay, at last to the example, then.
Here is one way to program in a capability style using today's Python, relying on no changes to the interpreter. This example defines a "class" called DirectoryReader that provides read-only access to only a particular subtree of the filesystem.
class Namespace: def __init__(self, *args, **kw): for value in args: self.__dict__[value.__name__] = value for name, value in kw.items(): self.__dict__[name] = value
class ReadOnly(Namespace): def __setattr__(self, name, value): raise TypeError('read-only namespace')
def FileReader(path, name): self = Namespace(file=open(path, 'r'))
def __repr__(): return '<FileReader %r>' % name
def reset(): self.file.seek(0)
return ReadOnly(__repr__, reset, self.file.read, self.file.close)
def DirectoryReader(path, name): def __repr__(): return '<DirectoryReader %r>' % name
def list(): return os.listdir(path)
def readfile(name): fullpath = os.path.join(path, name) if os.path.isfile(fullpath): return FileReader(fullpath, name)
def getdir(name): fullpath = os.path.join(path, name) if os.path.isdir(fullpath): return DirectoryReader(fullpath, name)
return ReadOnly(__repr__, list, readfile, getdir)
Now, if we pass an instance of DirectoryReader to code running in restricted mode, i think this is actually secure.
Specifically, the only introspective attributes we have to disallow, in order for these objects to enforce their intended restrictions, are im_self and func_globals. Of course, we still have to hide __import__ and sys.modules if we want to prevent code from obtaining access to the filesystem in other ways.
Hiding __dict__, while it has no impact on restricting filesystem access, allows us to pass the same DirectoryReader object to two clients without inadvertently creating a communication channel between them.