devshell
A preset designed to be used as part of a development environment, for example to confine AI agents, or to limit the impact of potentially malicious dependencies / supply chain attacks, as seen in the past with providers like npm, cargo, or pypi.
Confines any application to the current directory (at time of execution), and provides persistence within the sandbox
for a number of commonly used directories (e.g. $HOME/.cache).
Additional read-write and read-only directories may be added by specifying the BWRAP_MOUNTS_RW and BWRAP_MOUNTS_RO
environment variables at runtime respectively. Both take in colon-separated lists of paths. For example:
cd ~/git/my-project
BWRAP_MOUNTS_RO="$HOME/git/my-other-project" nvim-bwrapped
This spawns a bwrapped nvim with read-write access to $HOME/git/my-project (since that’s the current
directory), and read-only access to $HOME/git/my-other-project. The two environment variables can be combined:
cd ~/git/my-project
BWRAP_MOUNTS_RO="$HOME/git/my-readonly-project:$HOME/git/my-other-readonly-project" BWRAP_MOUNTS_RW="$HOME/git/my-readwrite-project" nvim-bwrapped
This allows you to sandbox commonly used tools like text editors without having to create separate wrappers for each use case, having to move directories around, or invoking the tools from a parent directory, which may expose more information than is necessary.
Use via:
{
myPackage = pkgs.mkBwrapper {
imports = [
pkgs.bwrapperPresets.devshell
];
# your config here
};
}
Source reference:
{ lib, ... }:
let
inherit (lib) mkDefault;
in
{
config = {
fhsenv = {
opts.unshareNet = mkDefault false;
bwrap.additionalArgs = [
''"''${user_rw_binds[@]}"''
''"''${user_ro_binds[@]}"''
];
};
mounts = {
readWrite = [
"$PWD"
];
sandbox = [
{
name = "config";
path = "$HOME/.config";
}
{
name = "local";
path = "$HOME/.local";
}
{
name = "cache";
path = "$HOME/.cache";
}
{
name = "cargo";
path = "$HOME/.cargo";
}
];
};
script.preCmds.stage3 = ''
declare -a user_rw_binds
# Parse BWRAP_MOUNTS_RW: colon-separated list of paths to bind-mount read-write.
# User-specified and trusted. Sanitized to prevent weird characters or spaces
# from breaking the script. Paths canonicalized to absolute form via realpath.
if [[ -n "''${BWRAP_MOUNTS_RW:-}" ]]; then
BWRAP_SAVE_IFS="$IFS"
IFS=':'
read -r -a bwrap_mount_paths <<< "$BWRAP_MOUNTS_RW"
IFS="$BWRAP_SAVE_IFS"
for raw_path in "''${bwrap_mount_paths[@]}"; do
# Skip empty entries (consecutive/leading/trailing colons)
if [[ -z "$raw_path" ]]; then continue; fi
# Canonicalize: resolve . and .. components, make path absolute
sanitized="$(realpath -m -- "$raw_path" 2>/dev/null)" || sanitized=""
# Skip if canonicalization failed or produced non-absolute path
if [[ -z "$sanitized" ]]; then continue; fi
if [[ "$sanitized" != /* ]]; then continue; fi
user_rw_binds+=("--bind" "$sanitized" "$sanitized")
done
fi
declare -a user_ro_binds
if [[ -n "''${BWRAP_MOUNTS_RO:-}" ]]; then
BWRAP_SAVE_IFS="$IFS"
IFS=':'
read -r -a bwrap_mount_paths <<< "$BWRAP_MOUNTS_RO"
IFS="$BWRAP_SAVE_IFS"
for raw_path in "''${bwrap_mount_paths[@]}"; do
if [[ -z "$raw_path" ]]; then continue; fi
sanitized="$(realpath -m -- "$raw_path" 2>/dev/null)" || sanitized=""
if [[ -z "$sanitized" ]]; then continue; fi
if [[ "$sanitized" != /* ]]; then continue; fi
user_ro_binds+=("--ro-bind" "$sanitized" "$sanitized")
done
fi
'';
};
meta = {
name = "devshell";
description = ''
A preset designed to be used as part of a development environment, for example to confine AI agents, or to limit the
impact of potentially malicious dependencies / supply chain attacks, as seen in the past with providers like npm,
cargo, or pypi.
Confines any application to the current directory (at time of execution), and provides persistence within the sandbox
for a number of commonly used directories (e.g. `$HOME/.cache`).
Additional read-write and read-only directories may be added by specifying the `BWRAP_MOUNTS_RW` and `BWRAP_MOUNTS_RO`
environment variables at runtime respectively. Both take in colon-separated lists of paths. For example:
```bash
cd ~/git/my-project
BWRAP_MOUNTS_RO="$HOME/git/my-other-project" nvim-bwrapped
```
This spawns a bwrapped `nvim` with read-write access to `$HOME/git/my-project` (since that's the current
directory), and read-only access to `$HOME/git/my-other-project`. The two environment variables can be combined:
```bash
cd ~/git/my-project
BWRAP_MOUNTS_RO="$HOME/git/my-readonly-project:$HOME/git/my-other-readonly-project" BWRAP_MOUNTS_RW="$HOME/git/my-readwrite-project" nvim-bwrapped
```
This allows you to sandbox commonly used tools like text editors without having to create separate wrappers for each
use case, having to move directories around, or invoking the tools from a parent directory, which may expose more
information than is necessary.
'';
};
}