Repositories / jai.git
jai.git
Clone (read-only): git clone http://git.guha-anderson.com/git/jai.git
@@ -8,9 +8,9 @@ const std::string jai_defaults = # conf .defaults # # You can override settings in this file by editing it directly or by -# adding configuration directives to default.conf or other <name>.conf -# files after the `conf .defaults` line. (Later lines override -# previous ones in configuration files.) +# appending configuration directives to default.conf or other +# <name>.conf files after the `conf .defaults` line. (Later lines +# override previous ones in configuration files.) # # If you delete this file, jai will re-create it the next time it # runs. You can also see the default contents of this file by running @@ -125,5 +125,11 @@ unsetenv *_TOKEN )"; extern const std::string default_conf = - R"(conf .defaults + R"(# The following line includes sensible defaults from the file +# .defaults. You can override these defaults by appending +# configuration options to this file. See the .defaults file or the +# jai(1) man page for details. + +conf .defaults + )";
@@ -8,12 +8,13 @@ jai - Jail an AI agent # SYNOPSIS +`jai` `--init` \ `jai` [*option*]... [*cmd* [*arg*]...] \ `jai` `-u` # DESCRIPTION -`jai` is a super lightweight sandbox for AI agents requiring almost no +`jai` is a super-lightweight sandbox for AI agents requiring almost no configuration. By default it provides casual security, so is not a substitute for using a proper container to confine agents. However, it is a great alternative to using no protection at all when you are @@ -54,13 +55,13 @@ option. If you use casual mode and forget to export some directory that you updated in the jail, you will find changed files in -`$HOME/.jai/default.changes`. You can destroy the sandbox with `jai --u`, move the changed files back into your home directory, and re-run -`jai` with the appropriate `-d` flag. +`$HOME/.jai/default.changes`. You can destroy the jail with `jai -u`, +move the changed files back into your home directory, and re-run `jai` +with the appropriate `-d` flag. -jai allows the use of multiple sandboxed home directories. To use a -home directory other than the default, just give it a name with the -`-n` option and it will be created on demand. When you specify a home +jai allows the use of multiple jailed home directories. To use a home +directory other than the default, just give it a name with the `-n` +option and it will be created on demand. When you specify a home directory with `-n`, strict mode becomes the default (unless there is no unprivileged `jai` user on your system, in which case jai falls back to bare mode). It is possible to have multiple home overlays by @@ -108,6 +109,10 @@ environment before running the command. # OPTIONS +`--init` +: Create default configuration files and exit. You should run this + first, before activating any jails. + `-C` *file*, `--conf `*file* : Specifies the configuration file to read. If *file* does not contain a `/`, the file is relative to `$HOME/.jai`. @@ -143,22 +148,22 @@ environment before running the command. : Set jai's execution mode. In casual mode, the user's home directory is made available as an overlay mount. Casual mode protects against destruction of files outside of granted directories, but does not - protect confidentiality: sandboxed code can read most files - accessible to the user. You can hide specific files with the - `--mask` option or by deleting them under `/run/jai/$USER/*.home`, - but because casual mode makes everything readable by default, it - cannot protect all sensitive files. + protect confidentiality: jailed code can read most files accessible + to the user. You can hide specific files with the `--mask` option + or by deleting them under `/run/jai/$USER/*.home`, but because + casual mode makes everything readable by default, it cannot protect + all sensitive files. In strict mode, the user's home directory is replaced by an empty - directory (`$HOME/.jai/`*name*`.home`), and sandboxed code runs with - a different user id, `jai`. Id-mapped mounts are used to map `jai` - to the invoking user in granted directories. Strict mode is the - default when you name a sandbox (see `--name`), but not for the - default sandbox. + directory (`$HOME/.jai/`*name*`.home`), and jailed code runs with a + different user id, `jai`. Id-mapped mounts are used to map `jai` to + the invoking user in granted directories. Strict mode is the + default when you name a jail (see `--name`), but not for the default + sandbox. Bare mode uses an empty directory like strict mode, but runs with the invoking user's credentials. It is inferior to strict mode, but - can be used for NFS-mounted home directories, since NFS does not + can be used for NFS-mounted home directories since NFS does not support id-mapped mounts. `-n` *name*, `--name` *name*
@@ -85,7 +85,7 @@ struct Config { } int home(); - int home_jai(); + int home_jai(bool create = false); int storage(); int run_jai(); int run_jai_user(); @@ -205,7 +205,7 @@ GECOS field is not "{}")", } const char *jcd = getenv("JAI_CONFIG_DIR"); - homejaipath_ = homejaipath_ / (jcd ? jcd : ".jai"); + homejaipath_ = homepath_ / (jcd ? jcd : ".jai"); // Paranoia about ptrace, because we will drop privileges to access // the file system as the user. @@ -245,10 +245,23 @@ Config::check_user(int fd, std::string p, bool untrusted_ok) } int -Config::home_jai() +Config::home_jai(bool create) { - if (!home_jai_fd_) - home_jai_fd_ = ensure_udir(home(), homejaipath_); + if (!home_jai_fd_) { + if (create) + home_jai_fd_ = ensure_udir(home(), homejaipath_); + else if (Fd fd = openat(home(), homejaipath_.c_str(), + O_RDONLY | O_DIRECTORY | O_CLOEXEC)) { + check_user(*fd); + home_jai_fd_ = std::move(fd); + } + else if (errno == ENOENT) { + err("{} does not exist; run {} --init to create it", + fdpath(home(), homejaipath_), prog.filename().string()); + } + else + syserr("{}", fdpath(home(), homejaipath_)); + } return *home_jai_fd_; } @@ -601,10 +614,10 @@ Config::make_mnt_ns() err("{} should be empty in jail", kRunRoot); Fd source = clone_tree(*empty); xmnt_setattr(*source, mount_attr{ - .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | - MOUNT_ATTR_NODEV, - .propagation = MS_PRIVATE, - }); + .attr_set = MOUNT_ATTR_RDONLY | + MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV, + .propagation = MS_PRIVATE, + }); xmnt_move(*source, *target); } @@ -1018,10 +1031,14 @@ do_main(int argc, char **argv) bool opt_u{}; std::vector<path> opt_d; path opt_C = ""; + bool opt_init{}; auto opts = conf.opt_parser(); // A few options not available in config files (*opts)("-u", [&] { opt_u = true; }, "Unmount sandboxed file systems"); + (*opts)( + "--init", [&] { opt_init = true; }, + "Create initial configuration files and exit"); // Override inline conf to make CLI idempotent (*opts)( "-C", "--conf", [&](path p) { opt_C = p; }, @@ -1050,6 +1067,15 @@ The default is CMD.conf if it exists, otherwise default.conf)", if (!conf.mask_files_.empty()) conf.mask_warn_ = true; + ensure_file(conf.home_jai(opt_init), ".defaults", jai_defaults, 0600); + ensure_file(conf.home_jai(), "default.conf", default_conf, 0600); + + if (opt_init) { + std::println("You can edit the configuration in {}/default.conf", + conf.homejaipath_.string()); + exit(0); + } + if (opt_u) { if (!conf.grant_cwd_ || !conf.grant_directories_.empty() || !cmd.empty()) { std::println(stderr, "-u is not compatible with -d, -D, or a command"); @@ -1060,9 +1086,6 @@ The default is CMD.conf if it exists, otherwise default.conf)", return; } - ensure_file(conf.home_jai(), ".defaults", jai_defaults, 0600); - ensure_file(conf.home_jai(), "default.conf", default_conf, 0600); - if (!opt_C.empty()) { if (!conf.parse_config_file(opt_C)) err("{}: no such configuration file", opt_C.string());