Repositories / jai.git
jai.git
Clone (read-only): git clone http://git.guha-anderson.com/git/jai.git
@@ -391,7 +391,7 @@ try_read_file(int dfd, path file) Fd fdholder; int fd = dfd; if (!file.empty()) { - fdholder = openat(fd, file.c_str(), O_RDONLY, O_CLOEXEC); + fdholder = openat(fd, file.c_str(), O_RDONLY | O_CLOEXEC); if (!fdholder) return std::unexpected( std::system_error(errno, std::system_category(), fdpath(fd, file)));
@@ -8,31 +8,32 @@ jai - Jail an AI agent # SYNOPSIS -`jai` [`-d` *dir*] [`-h` *name*] [`-D`] *cmd* [*arg* ...] \ -`jai` \ +`jai` [*option*]... *cmd* [*arg*]... \ +`jai` [`--casual`] [*option*]... \ `jai` `-u` # DESCRIPTION `jai` is a super lightweight sandbox for AI agents requiring almost no -configuration. It provides only casual security, so is not a +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 when you are thinking -of giving an agent full control of your account and all its files. -Compared to the latter, `jai` can reduce the blast radius should -things go wrong. +it is a great alternative to using no protection at all when you are +thinking of giving an agent full control of your account and all its +files. Compared to the latter, `jai` can reduce the blast radius +should things go wrong. -Before using `jai`, if your home directory is on NFS, make -`$HOME/.jai` a symbolic link to a directory you own on a local file -system supporting extended attributes. +Before using `jai`, if your home directory is on NFS and you want to +use casual mode, make `$HOME/.jai` a symbolic link to a directory you +own on a local file system that supports extended attributes. +Otherwise, you may only be able to use strict mode. To get started, run `jai` with no arguments. (If it is not setuid root, you will need to run `sudo jai`.) This will create an overlay -mount of your home directory in `/run/jai/$USER/sandboxed-home`. -Change to that directory and delete any sensitive files you don't want -your agent to have access to. (Start with deleting a file you don't -care about, and verify that it only disappears from the sandbox, not -from your real home directory.) +mount of your home directory in `/run/jai/$USER/default.home`. Change +to that directory and delete any sensitive files you don't want your +agent to have access to. (Start with deleting a file you don't care +about, and verify that it only disappears from the sandbox, not from +your real home directory.) Once you are satisfied with the sandbox, go to a project directory you own and run `jai $SHELL`. That will let you explore the sandboxed @@ -60,12 +61,14 @@ flag. # OPTIONS -`-d` *dir* +`-d` *dir*, `--dir=`*dir* : Grant full access to directory *dir* and everything below in the jail. You must own the directory. You can supply this option multiple times. -`-D` +`-C` *file*, `--conf=`*file* + +`-D`, `--nocwd` : By default, `jai` grants access to the current working directory even if it is not specified with `-d`. This option suppresses that behavior. If you run with `-D` and no `-d` options, your entire
@@ -107,14 +107,13 @@ struct Config { bool Config::parse_config_file(path file, Options *opts) { - auto ld = homepath_ / file; - if (auto [_it, ok] = config_loop_detect_.insert(ld); !ok) { - warn("{}: configuration loop", file.string()); - return false; - } + bool slash = std::ranges::distance(file.begin(), file.end()) > 1; + auto ld = (slash ? cwd() : homepath_ / ".jai") / file; + if (auto [_it, ok] = config_loop_detect_.insert(ld); !ok) + err<Options::Error>("configuration loop"); Defer _clear{[this, ld = std::move(ld)] { config_loop_detect_.erase(ld); }}; - auto r = try_read_file(home_jai(), file); + auto r = try_read_file(slash ? AT_FDCWD : home_jai(), file); if (!r) { if (r.error().code() == std::errc::no_such_file_or_directory) return false; @@ -189,7 +188,8 @@ Try running "sudo systemd-sysusers".)", // Paranoia about ptrace, because we will drop privileges to access // the file system as the user. - prctl(PR_SET_DUMPABLE, 0); + if (prctl(PR_SET_DUMPABLE, 0) == -1) + syserr("prctl(PR_SET_DUMPABLE, 0)"); old_umask_ = umask(0); } @@ -361,7 +361,7 @@ Fd Config::make_idmap_ns() { pid_t pid{-1}; - Defer _reap([pid] { + Defer _reap([&pid] { if (pid > 0) { while (waitpid(pid, nullptr, 0) == -1 && errno == EINTR) ; @@ -627,7 +627,7 @@ Config::exec(int nsfd, char **argv) if (auto pid = xfork()) { close(nsfd); - int status; + int status = -1; while (waitpid(pid, &status, 0) == -1 && errno == EINTR) ; // unmount(); @@ -794,10 +794,13 @@ default: CMD.conf or default.conf if CMD.conf does not exist)", return; } - if (opt_C.empty() && !cmd.empty() && conf.name_ok(cmd[0])) - opt_C = std::format("{}.conf", cmd[0]); - if ((opt_C.empty() || !conf.parse_config_file(opt_C)) && - !conf.parse_config_file("default.conf")) { + if (!opt_C.empty()) { + if (!conf.parse_config_file(opt_C)) + err("{}: no such configuration file", opt_C.string()); + } + else if ((cmd.empty() || !conf.name_ok(cmd[0]) || + !conf.parse_config_file(std::format("{}.conf", cmd[0]))) && + !conf.parse_config_file("default.conf")) { conf.make_default_conf(); conf.parse_config_file("default.conf"); }