|

feat(install,remove,upgrade): enable chroot confinement with upfront privilege abort

Author: Chris Tusa <chris.tusa@leafscale.com>
Date: Jul 05, 2026 17:35
Changeset: 20e96d779dee115542c500bf41935c0a3b2c9d64
Branch: default

Diff

diff -r 6a5359a64b22 -r 20e96d779dee src/commands/install.reef
--- a/src/commands/install.reef	Sun Jul 05 17:20:31 2026 -0500
+++ b/src/commands/install.reef	Sun Jul 05 17:35:42 2026 -0500
@@ -40,6 +40,7 @@
 import util.version as ver
 import types
 import util.color
+import util.priv
 import exitcodes as ec
 
 export
@@ -387,9 +388,17 @@
     end if
 
     let install_root = get_root(cfg)
-    // Phase 1: chroot disabled (chroot_scripts=false), so ctx.do_chroot is always false;
-    // this exports $CORAL_ROOT to scripts. Phase 2 replaces the args with cfg + is_root().
-    let script_ctx = package.resolve_script_ctx(install_root, false, true)
+    // Resolve script execution context; abort early if confinement is required but impossible.
+    // Defense-in-depth: main.reef already bars non-root for all non-query commands, so this abort
+    // is currently unreachable via the CLI. Kept so confinement fails safely if that global gate is
+    // ever relaxed.
+    if package.script_wants_chroot(install_root, config.get_chroot_scripts(cfg)) and not priv.is_root()
+        color.print_error("--root " + install_root + " runs maintainer scripts confined to the target via chroot, which requires root privileges.")
+        color.print_error("Re-run as root, or pass --no-chroot-scripts to run them on the host (unconfined).")
+        cleanup_extract_dir(extract_dir)
+        return false
+    end if
+    let script_ctx = package.resolve_script_ctx(install_root, config.get_chroot_scripts(cfg), priv.is_root())
 
     if is_upgrade
         // Upgrade flow: pre-upgrade → remove old files → install new files → post-upgrade
diff -r 6a5359a64b22 -r 20e96d779dee src/commands/remove.reef
--- a/src/commands/remove.reef	Sun Jul 05 17:20:31 2026 -0500
+++ b/src/commands/remove.reef	Sun Jul 05 17:35:42 2026 -0500
@@ -35,6 +35,7 @@
 import util.color
 import fs.stat
 import fs.ops
+import util.priv
 import exitcodes as ec
 
 export
@@ -143,7 +144,7 @@
     mut all_success = true
     i = 0
     while i < pkg_count
-        if not remove_package(packages[i], root_path)
+        if not remove_package(packages[i], root_path, cfg)
             color.print_error("Failed to remove: " + packages[i])
             all_success = false
         end if
@@ -183,7 +184,7 @@
     println("  coral remove --force libfoo")
 end print_usage
 
-fn remove_package(pkg_name: string, root_path: string): bool
+fn remove_package(pkg_name: string, root_path: string, cfg: config.Config): bool
     // Check if package is installed (use rooted version if root is set)
     if not check_installed(pkg_name, root_path)
         color.print_error(pkg_name + " is not installed")
@@ -192,10 +193,17 @@
 
     color.print_action("Removing " + pkg_name + "...")
 
-    // Phase 1: chroot disabled (chroot_scripts=false), so ctx.do_chroot is always false;
-    // this exports $CORAL_ROOT to scripts. Phase 2 replaces the args with cfg + is_root().
     let install_root = get_actual_root(root_path)
-    let script_ctx = package.resolve_script_ctx(install_root, false, true)
+    // Resolve script execution context; abort early if confinement is required but impossible.
+    // Defense-in-depth: main.reef already bars non-root for all non-query commands, so this abort
+    // is currently unreachable via the CLI. Kept so confinement fails safely if that global gate is
+    // ever relaxed.
+    if package.script_wants_chroot(install_root, config.get_chroot_scripts(cfg)) and not priv.is_root()
+        color.print_error("--root " + install_root + " runs maintainer scripts confined to the target via chroot, which requires root privileges.")
+        color.print_error("Re-run as root, or pass --no-chroot-scripts to run them on the host (unconfined).")
+        return false
+    end if
+    let script_ctx = package.resolve_script_ctx(install_root, config.get_chroot_scripts(cfg), priv.is_root())
 
     // Get package info (use rooted version if root is set)
     let pkg = get_installed_pkg(pkg_name, root_path)