Repositories / jai.git

jai.git

Clone (read-only): git clone http://git.guha-anderson.com/git/jai.git

Branch

mask each time overlay is mounted

Author
David Mazieres <dm@uun.org>
Date
2026-03-15 20:15:41 -0700
Commit
9ba225b657d1a4c5fe982a0b036e82f8ccb8b7dd
Makefile.am
index 850ee39..657a888 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,8 @@ bin_PROGRAMS = jai
 
 AM_CXXFLAGS = $(MOUNT_CFLAGS) $(LIBACL_CFLAGS)
 
-jai_SOURCES = cred.cc fs.cc jai.cc cred.h argtype.h defer.h err.h fs.h jai.h
+jai_SOURCES = cred.cc default_conf.cc fs.cc jai.cc cred.h argtype.h	\
+defer.h err.h fs.h jai.h
 jai_LDADD = $(MOUNT_LIBS) $(LIBACL_LIBS)
 
 man1_MANS = jai.1
default_conf.cc
new file mode 100644
index 0000000..53a2e7e
--- /dev/null
+++ b/default_conf.cc
@@ -0,0 +1,92 @@
+
+#include "jai.h"
+
+const std::string default_conf =
+    R"(# Instead of copying this file to create a new configuration, you can
+# it the file by referernce using a conf command.  Don't uncomment in
+# this example line default.conf or you will create a loop.  Example:
+#
+# conf default.conf
+
+# The default mode is strict for all named sandboxes and casual for
+# the default sandbox.  A strict sandbox runs under the dedicated jai
+# UID and starts with an empty home directory.  A casual sandbox runs
+# with your own UID and makes your home directory copy-on-write via an
+# overlay mount.  To change the default, you can uncomment one of the
+# following:
+
+# casual
+# strict
+
+# jai launches programs in a sandbox by running bash with the command
+# name in "$0" and the arguments in "@".  bash will have a PID 1,
+# which can confuse some programs.  Adding "; exit $?" after the
+# command and arguments will cause bash to fork and stay around,
+# giving "$0" PID 2.  For non-default configurations, you can also use
+# a command directive to insert extra arguments or set environment
+# variables.
+
+command "$0" "$@"; exit $?
+
+# Masked files are deleted when an overlayfs is first created, but
+# have no effect on existing overlays.  To delete files from an
+# existing overlay, delete them under /run/jai/$USER/default.home.
+# Otherwise, to apply new mask directives you can run "jai -u" to
+# unmount any existing overlays.
+
+mask .jai
+mask .ssh
+mask .gnupg
+mask .local/share/keyrings
+mask .netrc
+mask .git-credentials
+mask .aws
+mask .azure
+mask .config/gcloud
+mask .config/gh
+mask .config/Keybase
+mask .config/kube
+mask .docker
+mask .password-store
+mask .mozilla
+mask .config/chromium
+mask .config/google-chrome
+mask .config/BraveSoftware
+mask .bash_history
+mask .zsh_history
+
+# The following environment variables will be removed from sandboxed
+# environments.  You can use * as a wildcard to match any variables
+# matching the pattern.
+
+unsetenv AZURE_CLIENT_ID
+unsetenv AZURE_TENANT_ID
+unsetenv DATABASE_URL
+unsetenv MONGO_URI
+unsetenv MONGODB_URI
+unsetenv REDIS_URL
+unsetenv GOOGLE_APPLICATION_CREDENTIALS
+unsetenv KUBECONFIG
+unsetenv BB_AUTH_STRING
+unsetenv SENTRY_DSN
+unsetenv SLACK_WEBHOOK_URL
+unsetenv *_ACCESS_KEY
+unsetenv *_API_KEY
+unsetenv *_APIKEY
+unsetenv *_AUTH
+unsetenv *_AUTH_TOKEN
+unsetenv *_CONNECTION_STRING
+unsetenv *_CREDENTIAL
+unsetenv *_CREDENTIALS
+unsetenv *_PASSWD
+unsetenv *_PASSWORD
+unsetenv *_PID
+unsetenv *_PRIVATE_KEY
+unsetenv *_PWD
+unsetenv *_SECRET
+unsetenv *_SECRET_KEY
+unsetenv *_SOCK
+unsetenv *_SOCKET
+unsetenv *_SOCKET_PATH
+unsetenv *_TOKEN
+)";
fs.cc
index 01bd2bf..57df449 100644
--- a/fs.cc
+++ b/fs.cc
@@ -236,21 +236,27 @@ make_whiteout(int dfd, const path &inp)
     err<std::logic_error>(R"(make_whiteout: "{}" is not a relative path)",
                           inp.string());
 
-  Fd dirholder;
-  if (p.has_parent_path()) {
-    dirholder = ensure_dir(dfd, p.parent_path(), 0700, kNoFollow, false);
-    dfd = *dirholder;
-    p = p.filename();
-  }
+  try {
+    Fd dirholder;
+    if (p.has_parent_path()) {
+      dirholder = ensure_dir(dfd, p.parent_path(), 0700, kNoFollow, false);
+      dfd = *dirholder;
+      p = p.filename();
+    }
 
-  auto olduid = geteuid();
-  seteuid(0);
-  int err = 0;
-  if (mknodat(dfd, p.filename().c_str(), S_IFCHR, 0))
-    err = errno;
-  seteuid(olduid);
-  if ((errno = err))
-    syserr("mknod {}/.jai c 0 0", fdpath(dfd, p));
+    auto olduid = geteuid();
+    seteuid(0);
+    int err = 0;
+    if (mknodat(dfd, p.filename().c_str(), S_IFCHR, 0))
+      err = errno;
+    seteuid(olduid);
+    if ((errno = err))
+      syserr("mknod {}/.jai c 0 0", fdpath(dfd, p));
+  } catch (const std::system_error &e) {
+    if (e.code() != std::errc::not_a_directory &&
+        e.code() != std::errc::file_exists)
+      throw;
+  }
 }
 
 bool
jai.cc
index 81df0dc..73ec912 100644
--- a/jai.cc
+++ b/jai.cc
@@ -95,8 +95,10 @@ struct Config {
   void mask_warn()
   {
     if (mask_warn_) {
-      warn("--mask did nothing because ~/.jai/{}.changes already existed",
-           sandbox_name_.string());
+      warn(R"(--mask ignored because {5}/{0}/{1}.home already mounted.
+{2:>{3}}  Run "{4} -u" to unmount overlays.)",
+           user_, sandbox_name_.string(), "", prog.filename().string().size(),
+           prog.filename().string(), kRunRoot);
       mask_warn_ = false;
     }
   }
@@ -293,10 +295,6 @@ Config::make_blacklist(int dfd, path name)
 {
   Fd blacklistfd = ensure_dir(dfd, name.c_str(), 0700, kFollow);
   check_user(*blacklistfd);
-  if (!is_dir_empty(*blacklistfd)) {
-    mask_warn();
-    return blacklistfd;
-  }
 
   for (path p : mask_files_) {
     try {
@@ -576,104 +574,13 @@ Config::unmountall()
   unlinkat(run_jai(), user_.c_str(), AT_REMOVEDIR);
 }
 
-auto env_blacklist = std::to_array<const char *>({
-    // Azure
-    "AZURE_CLIENT_ID",
-    "AZURE_TENANT_ID",
-    // Databases (connection URIs contain embedded credentials)
-    "DATABASE_URL",
-    "MONGO_URI",
-    "MONGODB_URI",
-    "REDIS_URL",
-    // GCP
-    "GOOGLE_APPLICATION_CREDENTIALS",
-    // Docker / K8s
-    "KUBECONFIG",
-    // Bitbucket
-    "BB_AUTH_STRING",
-    // Sentry
-    "SENTRY_DSN",
-    // Slack
-    "SLACK_WEBHOOK_URL",
-    // Suffixes
-    "*_ACCESS_KEY",
-    "*_API_KEY",
-    "*_APIKEY",
-    "*_AUTH",
-    "*_AUTH_TOKEN",
-    "*_CONNECTION_STRING",
-    "*_CREDENTIAL",
-    "*_CREDENTIALS",
-    "*_PASSWD",
-    "*_PASSWORD",
-    "*_PID",
-    "*_PRIVATE_KEY",
-    "*_PWD",
-    "*_SECRET",
-    "*_SECRET_KEY",
-    "*_SOCK",
-    "*_SOCKET",
-    "*_SOCKET_PATH",
-    "*_TOKEN",
-});
-
-const auto default_masklist = std::to_array<const char *>({
-    ".jai",
-    ".ssh",
-    ".gnupg",
-    ".local/share/keyrings",
-    ".netrc",
-    ".git-credentials",
-    ".aws",
-    ".azure",
-    ".config/gcloud",
-    ".config/gh",
-    ".config/Keybase",
-    ".config/kube",
-    ".docker",
-    ".password-store",
-    ".mozilla",
-    ".config/chromium",
-    ".config/google-chrome",
-    ".config/BraveSoftware",
-    ".bash_history",
-    ".zsh_history",
-});
-
 void
 Config::make_default_conf()
 {
   auto fd = xopenat(home_jai(), ".", O_RDWR | O_TMPFILE | O_CLOEXEC, 0600);
-  std::string text =
-      R"(# The executed process will have PID 1, which can confuse some
-# programs.  Adding "; exit $?" after the command and arguments will
-# cause bash to fork and stay around, so command "$0" has PID 2.
-
-command "$0" "$@"; exit $?
-
-# Masked file wills be deleted when a new overlayfs is first created,
-# but have no effect on existing overlays.  To delete files from an
-# existing overlay, delete them under /run/jai/$USER/default.home.  If
-# you want to start over with a fresh overlay, you can run "jai -u" to
-# unmount any existing overlays, then remove the directory
-# $HOME/.jai/$USER/default.changes.  The next time you run jai, it
-# will create a new overlay masking all of the files below.
-
-)";
-  for (auto p : default_masklist)
-    text += std::format("mask {}\n", p);
-
-  text += R"(
-# The following environment variables will be removed from sandboxed
-# environments.  You can use * as a wildcard to match any variables
-# matching the pattern.
-
-)";
-  for (auto e : env_blacklist)
-    text += std::format("unsetenv {}\n", e);
-
   errno = EAGAIN;
-  if (write(*fd, text.data(), text.size()) != text.size())
+  if (write(*fd, default_conf.data(), default_conf.size()) !=
+      default_conf.size())
     syserr("write(O_TMPFILE for default.conf)");
   if (linkat(*fd, "", home_jai(), "default.conf", AT_EMPTY_PATH) &&
       errno != EEXIST)
jai.h
index 82ec24a..37a9364 100644
--- a/jai.h
+++ b/jai.h
@@ -26,3 +26,5 @@ xfork(std::uint64_t flags = 0)
       exit(1);                                                                 \
     }                                                                          \
   } while (0)
+
+extern const std::string default_conf;