Hello Guix, I recently had a discussion in #spritely on Libera.Chat about Guix and Nix, and in particular a (relatively) new feature of Nix called flakes that Guix doesn't currently have an analogue for.
I've been a Guix user for a while, but I've only recently started looking at using Guix for development via ~guix shell~ as in this blog post by David Thompson[0]. The Guile port of Spritely has been using it, so I've been trying it out for one of my own projects as a result. And it seems pretty nice; you're using the same package definitions you might use when contributing a package upstream to Guix, which feels pretty natural as someone already pretty familiar with Guix as a user. However, I noticed something about the resulting dependency graph that feels somewhat unsatisfying. When you define a package, the dependencies you provide in e.g. ~inputs~ are references to packages. And the way you get those is, of course, by importing modules containing those packages. But the package you end up with each time you do that... depends on which revision of Guix you're running when you run ~guix shell~! So if I point someone to a project with a ~guix.scm~ file, they might not be able to use it if their Guix revision is too old. (Or too new, if packages have been renamed or removed.) More generally, it means that they do not end up with the same dependency graph that I do. This makes troubleshooting potentially tricky, because if something breaks you have to check the resulting profile to see which versions of your package's dependencies (and transitive dependencies) are actually installed. For those who haven't used Nix, it has a solution to this called flakes. Flakes let you specify git repositories explicitly as inputs for your project[1]. (It also maintains a flake.lock file so you can lock to a specific revision automatically while still using a named branch in your inputs directly, but I believe you could in theory refer to a specific rev in your inputs.) Effectively, the channels you're using for dependencies are specified by the project you're building, not whatever happens to be configured on your local machine. I think something like this would be useful for Guix for many of the same reasons it's useful in Nix. But there's a bit of a security conundrum here. Loading Guix package definitions involves code execution, which as far as I can tell isn't currently sandboxed at all! And that's a problem. When you load package definitions from a channel that you've configured on your system, you've explicitly trusted that channel's maintainers. But with a flake-like system... even if you might be okay depending on someone else's code, that doesn't necessarily mean you fully trust them. You might ultimately choose to sandbox the resulting binary, but that's moot if you can't fetch its dependencies without running arbitrary code with all of your user's authority. I think there is a solution to this, though. Right now when you evaluate Guix package definitions, you're basically running arbitrary Guile code. This of course can do anything you can do. But it doesn't have to! If you're familiar with Christine Lemmer-Webber's work on Spritely, you'll probably know what I'm getting at here: I think using object capabilities[2] would fix this. I recommend reading the linked blog post for a good explainer on what object capabilities are, as I won't do it justice here, but to perhaps oversimplify: code in a capability system only has access to the things you give it, and no more. It's like lexical scope, but taken very seriously. If you think about what a typical package definition needs to be able to do to your system directly, I think it's not actually that much? My (admittedly basic, possibly flawed) understanding of how Guix works is that most of the heavy lifting is done by ~guix-daemon~, which itself is pretty heavily sandboxed, and that most of what the ~guix~ subcommands are doing is building derivations which instruct ~guix-daemon~ to perform build actions. So while you're building these derivations, unless I'm misunderstanding: - You don't need network access - You don't need (much) filesystem access I think object capabilities provide a good answer to this problem. Rather than evaluating package definitions from a channel as you would normally run Guile code, evaluate them in a restricted environment that only has access to things you've passed in. In JavaScript, this might look like this (taken from this blog post[3] about the event-stream incident): #+BEGIN_SRC javascript const addHeader = require('./addHeader', {fs, https}); #+END_SRC This way, you could import modules including packages you'd like to use as dependencies, and if you don't pass those modules access to the rest of your filesystem they won't have it, and can't do things like cryptolocker your home directory. (At least not until you run some software installed from it, but that's a separate issue!) Of course, easier said than done. Guile's import system doesn't work like this. But I believe the Spritely project has a module system like this planned for Guile, which could enable things like this. I'm sure such a thing would be a lot of work, but I hope this plants a seed in your minds as to what might be possible. [0] https://dthompson.us/guix-for-development.html [1] https://nixos.wiki/wiki/Flakes#Introduction [2] http://habitatchronicles.com/2017/05/what-are-capabilities/ [3] https://medium.com/agoric/pola-would-have-prevented-the-event-stream-incident-45653ecbda99 -- Jonathan Frederickson