From b3919f782dcc5bb0e5cbd9c992e798eacd0ae10f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 31 Oct 2026 09:44:19 +0100 Subject: [PATCH 1/2] mind-self-run: MIND_SELF_SCRUB=off in the unit turns the child's subprocess scrub off; anything else keeps it on Co-Authored-By: Claude Sonnet 5.5 --- crates/mind-body/src/self_run.rs | 10 +++++++++- .../src/self_run/tests/process/lease_child.rs | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/mind-body/src/self_run.rs b/crates/mind-body/src/self_run.rs index 70767f9..eb88e01 100644 --- a/crates/mind-body/src/self_run.rs +++ b/crates/mind-body/src/self_run.rs @@ -90,6 +90,9 @@ pub const SEED_VARIABLE: &str = "MIND_SELF_CONFIG_SEED"; const TOKEN_FD_VARIABLE: &str = "CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR"; /// Tells `claude` to scrub credentials from the environment of what it starts. const ENV_SCRUB_VARIABLE: &str = "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB"; +/// Optional, set by the unit: `off` turns that scrub off for a run whose user holds root, where it +/// protects nothing and its sandbox's no_new_privs would only stop `sudo`. Anything else keeps it on. +pub const SCRUB_VARIABLE: &str = "MIND_SELF_SCRUB"; /// The name the start message tells her to admit as. pub const AGENT: &str = "mind-self-run"; /// The namespace of the session id: the run id is the name in it. @@ -761,6 +764,11 @@ fn child_environment(unit: &[(OsString, OsString)]) -> Vec<(&OsString, &OsString unit.iter().filter(|(name, _)| child_may_see(name)).map(|(name, value)| (name, value)).collect() } +/// Whether the unit turned the child's subprocess scrub off: only `MIND_SELF_SCRUB=off` does. +fn scrub_is_off(unit: &[(OsString, OsString)]) -> bool { + unit.iter().any(|(name, value)| name == SCRUB_VARIABLE && value == "off") +} + /// Starts `program` with a duplicate of the token descriptor open in it, and /// closes that duplicate here the moment the child exists. The lease's own /// descriptor is close-on-exec, so this duplicate is the child's only way to the @@ -773,7 +781,7 @@ fn spawn_program(program: &Path, args: &[String], token: &TokenFd, unit: &[(OsSt command.args(args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::inherit()); command.env_clear().envs(child_environment(unit)); command.env("HOME", &config.0).env(CONFIG_DIR_VARIABLE, &config.0); - command.env(TOKEN_FD_VARIABLE, inheritable.number().to_string()).env(ENV_SCRUB_VARIABLE, "1"); + command.env(TOKEN_FD_VARIABLE, inheritable.number().to_string()).env(ENV_SCRUB_VARIABLE, if scrub_is_off(unit) { "0" } else { "1" }); // Its own process group, so that a signal reaches what the child started too. #[cfg(unix)] std::os::unix::process::CommandExt::process_group(&mut command, 0); diff --git a/crates/mind-body/src/self_run/tests/process/lease_child.rs b/crates/mind-body/src/self_run/tests/process/lease_child.rs index a64c422..eb9aa72 100644 --- a/crates/mind-body/src/self_run/tests/process/lease_child.rs +++ b/crates/mind-body/src/self_run/tests/process/lease_child.rs @@ -156,3 +156,22 @@ fn the_parent_closes_the_duplicate_it_lent_the_child() { child.stop(Stop::Kill); drain(&mut *child); } + +#[test] +fn the_child_scrubs_its_subprocesses_unless_the_unit_says_off() { + let dir = tempfile::tempdir().unwrap(); + let program = script(&dir, "echo \"scrub=$CLAUDE_CODE_SUBPROCESS_ENV_SCRUB\""); + let scrub_with = |value: Option<&str>| { + let mut unit = unit_environment(); + unit.extend(value.map(|value| (SCRUB_VARIABLE.into(), value.into()))); + let config = FreshConfig::create(dir.path(), None).unwrap(); + let mut child = spawn_program(&program, &[], &token_holding("tok-canary-7f3a"), &unit, config).unwrap(); + child.close_input(); + let (lines, _) = drain(&mut *child); + field(&lines, "scrub") + }; + assert_eq!(scrub_with(None), "1", "unset keeps the scrub on"); + assert_eq!(scrub_with(Some("on")), "1", "any other value keeps it on"); + assert_eq!(scrub_with(Some("OFF")), "1", "only the exact word turns it off"); + assert_eq!(scrub_with(Some("off")), "0", "a unit for a user that holds root turns it off"); +} From f1e8572ae22184769bc85ad4c5c26a3b1dfb75fd Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 31 Oct 2026 09:46:40 +0100 Subject: [PATCH 2/2] mind-self-run: the scrub variable name is private; the public surface is unchanged Co-Authored-By: Claude Sonnet 5.5 --- crates/mind-body/src/self_run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/mind-body/src/self_run.rs b/crates/mind-body/src/self_run.rs index eb88e01..a69c4f9 100644 --- a/crates/mind-body/src/self_run.rs +++ b/crates/mind-body/src/self_run.rs @@ -92,7 +92,7 @@ const TOKEN_FD_VARIABLE: &str = "CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR"; const ENV_SCRUB_VARIABLE: &str = "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB"; /// Optional, set by the unit: `off` turns that scrub off for a run whose user holds root, where it /// protects nothing and its sandbox's no_new_privs would only stop `sudo`. Anything else keeps it on. -pub const SCRUB_VARIABLE: &str = "MIND_SELF_SCRUB"; +const SCRUB_VARIABLE: &str = "MIND_SELF_SCRUB"; /// The name the start message tells her to admit as. pub const AGENT: &str = "mind-self-run"; /// The namespace of the session id: the run id is the name in it.