we need to protect our bot against attackers. crafting a custom environment allows us to expose just what we need and nothing more.
[with the bot code open from the last episode] so in the last episode we had this chat bot which you could teach facts that it would repeat back to you, and it stored the facts in the data directory on disk. problem is it could also read and write outside the data directory if you gave it the right input. with a little more code we can restructure this so even if you forget to sanitize your inputs, it still can't access the filesystem outside where it should. the best part is we don't have to change the code. [open main.fnl] we're going to construct a custom environment and run our code inside it. this is basically equivalent to running code with a new, isolated set of globals, or a sandbox if you prefer. our environment needs to be able to do I/O, so we give it print, plus io.read. (local env {:io {:read io.read : open} : print}) but instead of giving it io.open for files, we give it our own version of open which wraps the real one. (fn open [filename mode] (assert (legal-file? filename) (.. "disallowed file: " filename)) (io.open filename mode)) what constitutes a legal filename? well, it has to be start in the data directory, and it can't leave it by using ..: (fn legal-file? [filename] (and (filename:match "^data/") (not (filename:match "%.%.")))) once we've constructed our environment, we need to use it: (local fennel (require :fennel)) (fennel.dofile "bot.fnl" {: env}) there's one final step before we can run this; the environment needs to be populated with a few other basic globals. outside of print and io, these are all safe functions which can't leak any state out of the environment; it's mostly just some error handling: (local env {:io {:read io.read : open} : print :package {:loaded {}} :debug {:traceback debug.traceback} : string : type : assert : error : xpcall : pcall}) and we're off! our original bot.fnl code stays the same, but we'll launch netcat again pointed to our new entry point: $ nc -c "fennel main.fnl" localhost 6667 let's teach our bot some things: [switch back to #lodef] > lodefbot: set programming to fun > lodefbot: set netcat to useful > lodefbot: what is netcat? > lodefbot: set ../main.fnl to (steal-your "data") haha, welp! we prevented illegal access, but we crashed the whole thing. one tiny edit will make it more resilient. in a real program we'd give some kind of message back, but for now, just avoiding a crash is good enough. (while true (pcall handle (io.read))) if you want to learn more about this style of isolating your code and only passing in the capabilities it needs, I gave a whole talk on this topic at FennelConf, see the link below. there's also a link to pengbot, a fully-featured IRC bot which uses a more elaborate version of this technique. that's all I've got on this topic for now. thanks for watching, and hope to see you again in future lodef episodes.
copyright © 2023 Phil Hagelberg released under CC-BY-SA 4.0