Merge eureka-body/self-run-scrub-knob: MIND_SELF_SCRUB=off lets a root-holding Self run use sudo

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-31 09:50:13 +01:00
commit e5cd9d94e1
2 changed files with 28 additions and 1 deletions

View file

@ -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.
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);

View file

@ -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");
}