Currently code defaults to having all macros sandboxed. It's possible to disable the sandboxing altogether, but sometimes that's not ideal. What if you want to just allow a specific macro module to have access to things without opening it up to every macro?
The most basic improvement would be to disable the sandbox on a per-module basis.
This could be enabled by a command line flag such as
--compiler-sandbox-skip module1,my-module,macrotown
giving a
comma-delimited list of modules for which to disable the
sandbox. There is precedent for this in the existing flag
--skip-include module1,module2,module3
using a comma-delimited list
of module names, or --globals g1,g2
with identifiers rather than
module names.
We could build even more fine-grained access by specifying a list of globals to allow on a per-module basis. However, this seems less useful than it could be; really there are only three interesting things you would want to let thru:
io.open
(both read and write)io.popen
require
The most common option is going to be reading files. Macros which read
files are a really valid use case for non-pure-function macros, and so
we should definitely allow it. However, rather than just allowing
blanket access to all of io.open
we should allow you to specify a
directory to read from, and replace io.open
in that environment with
a function which checks the argument to ensure that it's reading from
the specified directory before passing on the call to the real io.open
.
Similarly we could let you specify that a given module is only allowed
to call io.popen
with a given string as its argument.
We could also allow you to specify that you want module X to be
allowed to require
an un-sandboxed version of module Y; for instance
if you want access to the network or something, but this seems like a
stretch; at this point it's probably better to just switch off the
sandboxing entirely?
At this point we are reaching the limit of what it's possible to convey thru command-line flags. The list of capabilities should be stored in a file and loaded by the compiler. Perhaps something like this:
$ fennel --macro-capabilities capabilities.fnl --compile foo.fnl
Where capabilities.fnl
would be something like this, loaded in a
strict sandboxed environment of course:
{:foo.macros {:read ["/path/to/config"
"resources/"]
:write ["/tmp"]}
:other.macros {:execute ["/sbin/ifconfig"]}}