← Back to context

Comment by homebrewer

3 months ago

I also use bubblewrap to isolate npm/pnpm/yarn (and everything started by them) from the rest of the system. Let's say all your source code resides in ~/code; put this somewhere in the beginning of your $PATH and name it `npm`; create symlinks/hardlinks to it for other package managers:

  #!/usr/bin/bash

  bin=$(basename "$0")

  exec bwrap \
    --bind ~/.cache/nodejs ~/.cache \
    --bind ~/code ~/code \
    --dev /dev \
    --die-with-parent \
    --disable-userns \
    --new-session \
    --proc /proc \
    --ro-bind /etc/ca-certificates /etc/ca-certificates \
    --ro-bind /etc/resolv.conf /etc/resolv.conf \
    --ro-bind /etc/ssl /etc/ssl \
    --ro-bind /usr /usr \
    --setenv PATH /usr/bin \
    --share-net \
    --symlink /tmp /var/tmp \
    --symlink /usr/bin /bin \
    --symlink /usr/bin /sbin \
    --symlink /usr/lib /lib \
    --symlink /usr/lib /lib64 \
    --tmpfs /tmp \
    --unshare-all \
    --unshare-user \
    "/usr/bin/$bin" "$@"

The package manager started through this script won't have access to anything but ~/code + read-only access to system libraries:

  bash-5.3$ ls -a ~
  .  ..  .cache  code

bubblewrap is quite well tested and reliable, it's used by Steam and (IIRC) flatpak.

Thanks, handy wrapper :) Note:

    --symlink /usr/lib /lib64 \

should probably be `/usr/lib64`

and

    --share-net \

should go after the `--unshare-all --unshare-user`

Also, my system doesn't have a symlink from /tmp to /var/tmp, so I'm guessing that's not needed for me (while /bin etc. are symlinks)

Very cool idea. Thanks for sharing. I made some minor tweaks based on feedback to your comment:

  #!/usr/bin/env bash
  #
  # See: https://news.ycombinator.com/item?id=45034496
  
  bin=$(basename "$0")
  
  echo "==========================="
  echo "Wrapping $bin in bubblewrap"
  echo "==========================="
  
  exec bwrap \
    --bind ~/.cache ~/.cache \
    --bind "${PWD}" "${PWD}" \
    --dev /dev \
    --die-with-parent \
    --disable-userns \
    --new-session \
    --proc /proc \
    --ro-bind /etc/ca-certificates /etc/ca-certificates \
    --ro-bind /etc/resolv.conf /etc/resolv.conf \
    --ro-bind /etc/ssl /etc/ssl \
    --ro-bind /usr /usr \
    --setenv PATH /usr/bin \
    --symlink /usr/bin /bin \
    --symlink /usr/bin /sbin \
    --symlink /usr/lib /lib \
    --symlink /usr/lib64 /lib64 \
    --tmpfs /tmp \
    --unshare-all \
    --unshare-user \
    --share-net \
    /usr/bin/env "$bin" "$@"

Notably `--share-net` should be moved down since it is negated by `--unshare-all`. I also added a reminder that the command is being bubblewrapped, modified the second read-write bind to the current directory, and changed the final exec to use `/usr/bin/env` to find the binary so it can be more flexible. I tested it with npm and yarn just now and it seems to work well. Thanks!

This is trading one distribution problem (npx) for another (bubblewrap). I think it’s a reasonable trade, but there’s no free lunch.

  • Not sure what this means. bubblewrap is as free as it gets, it's just a thin wrapper around the same kernel mechanisms used for containers, except that it uses your existing filesystems instead of creating a separate "chroot" from an OCI image (or something like it).

    The only thing it does is hiding most of your system from the stuff that runs under it, whitelisting specific paths, and optionally making them readonly. It can be used to run npx, or anything else really — just shove move symblinks into the beginning of your $PATH, each referencing the script above. Run any of them and it's automatically restricted from accessing e.g. your ~/.ssh

    https://wiki.archlinux.org/title/Bubblewrap