From 9b6eca787265657df286f233b365aa152132463d Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:19:41 -0500 Subject: [PATCH] fix(task): preserve essential env vars under deny_env on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit env_clear() in apply_sandbox() ran after the task executor had already populated Command with the filtered env (PATH, HOME, USER, SHELL, TERM, LANG plus any allow_env entries), wiping them all. Save the explicit envs, clear, then restore — matching the existing macOS path. Co-Authored-By: Claude Opus 4.7 (1M context) --- e2e/sandbox/test_sandbox_task | 8 ++++++++ src/cmd.rs | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/e2e/sandbox/test_sandbox_task b/e2e/sandbox/test_sandbox_task index 4574b5f940..3435ce4514 100644 --- a/e2e/sandbox/test_sandbox_task +++ b/e2e/sandbox/test_sandbox_task @@ -6,9 +6,17 @@ cat >mise.toml <<'TOML' [tasks.env_test] run = "echo ${SANDBOX_TASK_SECRET:-empty}" deny_env = true + +[tasks.path_test] +run = 'echo "PATH=${PATH:-unset} HOME=${HOME:-unset}"' +deny_env = true TOML export SANDBOX_TASK_SECRET="should_not_see" # Task with deny_env should not see the var assert "mise run env_test" "empty" + +# deny_env should still let essential vars (PATH, HOME, ...) through +assert_not_contains "mise run path_test" "PATH=unset" +assert_not_contains "mise run path_test" "HOME=unset" diff --git a/src/cmd.rs b/src/cmd.rs index 257ed400b0..dcbe59d754 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -622,9 +622,18 @@ impl<'a> CmdLineRunner<'a> { #[cfg(target_os = "linux")] { - // On Linux, clear inherited env before pre_exec so child only sees filtered vars + // On Linux, clear inherited env before pre_exec so child only sees filtered vars. + // env_clear() also wipes envs explicitly set via .envs(), so save and restore them. if sandbox.effective_deny_env() { + let saved: Vec<(std::ffi::OsString, std::ffi::OsString)> = self + .cmd + .get_envs() + .filter_map(|(k, v)| v.map(|v| (k.to_os_string(), v.to_os_string()))) + .collect(); self.cmd.env_clear(); + for (k, v) in saved { + self.cmd.env(k, v); + } } // Use pre_exec to apply Landlock/seccomp in the child process // before it execs the target program. This avoids restricting the mise process.