guix-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Guix, Nix flakes, and object capabilities


From: Jonathan Frederickson
Subject: Guix, Nix flakes, and object capabilities
Date: Tue, 28 Feb 2023 22:13:24 -0500
User-agent: mu4e 1.8.13; emacs 28.2

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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]