|
You might be interested in object-capability model[0] systems. It comes from the idea that in most memory-safe languages, before you can call a function or a method on an object, you first need to get a reference to it passed to you first. You can easily determine what code operates on an object by looking to where the object is passed. Now imagine if all types of IO interactions followed a similar system. Right now, most languages have "ambient authorities", references with imbued authority (IO capabilities, etc) that can be obtained by any code anywhere in the program. In nodejs, any code can use the globally-available `require('fs')` call to get a reference to the filesystem module and then use it to make changes to the filesystem freely; the filesystem module is an ambient authority. In a hypothetical object-capability version of nodejs, `require('fs')` would be invalid, and instead the application could have a single entry-point main function which receives the filesystem module as one of its parameters. In order to use functions that need to use the filesystem module, the main function would have to pass a reference to the filesystem module, or even a different object that follows the same interface. If it's known the function-to-be-called should only need to read files, then the function could be passed a wrapped version of the filesystem module that has all of its writing methods stubbed out for ones that throw errors instead. You can easily sandbox applications on a very granular level by passing them the minimum number of IO authority-imbued objects, and it's easy to review the security of code for looking where IO objects are passed around. Currently I think Haskell (with unsafe code disabled) is the closest thing to an object-capability language that's popular right now. Some of the terminology doesn't match up -- code doesn't get a reference to an IO monad to do IO, instead it must return an IO monad which gets mixed into the IO monad returned by the main function to take effect -- but I think many benefits come out about the same. There's no ambient authorities. You can follow the control flow to isolate the parts of the code that do IO. I'm not sure if it's possible in Haskell to do the equivalent of passing a restricted capability so easily; can you call an IO-monad-returning function (that was written without any sandboxing in mind) in a way that it's not allowed to write files? There are existing popular capability systems, but they're not as full as object-capability systems. They have object-capability-qualities, but only at the edges. A linux process can ask the OS to open a file and get a file handle, it can start a child process as another user or sandbox it in other ways so that the child is restricted from opening files itself, the parent can pass an individual file handle to the restricted child, etc. But outside of that specific file handling, the code of those processes isn't necessarily written in a very capability-style way. The child process may be written in C, it could put the file handle into a global variable, and any function inside its code could refer to that global variable. In an object-capability language, everything about the program's code follows the authority-comes-from-given-references object-capability style. [0] https://en.wikipedia.org/wiki/Object-capability_model |