This commit is contained in:
celia-oai
2026-03-16 17:48:45 -07:00
parent e0b4bf5bb6
commit 3a5081219d
2 changed files with 34 additions and 25 deletions

View File

@@ -60,11 +60,13 @@ pub(crate) fn build_command_spec(
/// the original script:
///
/// shell -lc "<script>"
/// => user_shell -c ". SNAPSHOT (best effort); exec shell -c <script>"
/// => user_shell -c ". SNAPSHOT (best effort); <script>"
///
/// This wrapper script uses POSIX constructs (`if`, `.`, `exec`) so it can
/// be run by Bash/Zsh/sh. On non-matching commands, or when command cwd does
/// not match the snapshot cwd, this is a no-op.
/// This wrapper script uses POSIX constructs (`if`, `.`) so it can be run by
/// Bash/Zsh/sh. Running the original script in the same shell process avoids
/// an additional shell re-exec, which matters for restricted read-only
/// sandboxes. On non-matching commands, or when command cwd does not match the
/// snapshot cwd, this is a no-op.
pub(crate) fn maybe_wrap_shell_lc_with_snapshot(
command: &[String],
session_shell: &Shell,
@@ -105,25 +107,23 @@ pub(crate) fn maybe_wrap_shell_lc_with_snapshot(
let snapshot_path = snapshot.path.to_string_lossy();
let shell_path = session_shell.shell_path.to_string_lossy();
let original_shell = shell_single_quote(&command[0]);
let original_script = shell_single_quote(&command[2]);
let snapshot_path = shell_single_quote(snapshot_path.as_ref());
let trailing_args = command[3..]
.iter()
.map(|arg| format!(" '{}'", shell_single_quote(arg)))
.collect::<String>();
let (override_captures, override_exports) = build_override_exports(explicit_env_overrides);
let rewritten_script = if override_exports.is_empty() {
format!(
"if . '{snapshot_path}' >/dev/null 2>&1; then :; fi\n\nexec '{original_shell}' -c '{original_script}'{trailing_args}"
"if . '{snapshot_path}' >/dev/null 2>&1; then :; fi\n\n{}",
command[2]
)
} else {
format!(
"{override_captures}\n\nif . '{snapshot_path}' >/dev/null 2>&1; then :; fi\n\n{override_exports}\n\nexec '{original_shell}' -c '{original_script}'{trailing_args}"
"{override_captures}\n\nif . '{snapshot_path}' >/dev/null 2>&1; then :; fi\n\n{override_exports}\n\n{}",
command[2]
)
};
vec![shell_path.to_string(), "-c".to_string(), rewritten_script]
let mut rewritten = vec![shell_path.to_string(), "-c".to_string(), rewritten_script];
rewritten.extend(command[3..].iter().cloned());
rewritten
}
fn build_override_exports(explicit_env_overrides: &HashMap<String, String>) -> (String, String) {

View File

@@ -33,7 +33,7 @@ fn maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell() {
let session_shell = shell_with_snapshot(
ShellType::Zsh,
"/bin/zsh",
snapshot_path,
snapshot_path.clone(),
dir.path().to_path_buf(),
);
let command = vec![
@@ -48,7 +48,7 @@ fn maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell() {
assert_eq!(rewritten[0], "/bin/zsh");
assert_eq!(rewritten[1], "-c");
assert!(rewritten[2].contains("if . '"));
assert!(rewritten[2].contains("exec '/bin/bash' -c 'echo hello'"));
assert!(rewritten[2].ends_with("\n\necho hello"));
}
#[test]
@@ -59,7 +59,7 @@ fn maybe_wrap_shell_lc_with_snapshot_escapes_single_quotes() {
let session_shell = shell_with_snapshot(
ShellType::Zsh,
"/bin/zsh",
snapshot_path,
snapshot_path.clone(),
dir.path().to_path_buf(),
);
let command = vec![
@@ -71,7 +71,7 @@ fn maybe_wrap_shell_lc_with_snapshot_escapes_single_quotes() {
let rewritten =
maybe_wrap_shell_lc_with_snapshot(&command, &session_shell, dir.path(), &HashMap::new());
assert!(rewritten[2].contains(r#"exec '/bin/bash' -c 'echo '"'"'hello'"'"''"#));
assert!(rewritten[2].ends_with("\n\necho 'hello'"));
}
#[test]
@@ -82,7 +82,7 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_bash_bootstrap_shell() {
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path,
snapshot_path.clone(),
dir.path().to_path_buf(),
);
let command = vec![
@@ -97,7 +97,7 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_bash_bootstrap_shell() {
assert_eq!(rewritten[0], "/bin/bash");
assert_eq!(rewritten[1], "-c");
assert!(rewritten[2].contains("if . '"));
assert!(rewritten[2].contains("exec '/bin/zsh' -c 'echo hello'"));
assert!(rewritten[2].ends_with("\n\necho hello"));
}
#[test]
@@ -123,7 +123,7 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_sh_bootstrap_shell() {
assert_eq!(rewritten[0], "/bin/sh");
assert_eq!(rewritten[1], "-c");
assert!(rewritten[2].contains("if . '"));
assert!(rewritten[2].contains("exec '/bin/bash' -c 'echo hello'"));
assert!(rewritten[2].ends_with("\n\necho hello"));
}
#[test]
@@ -134,7 +134,7 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_trailing_args() {
let session_shell = shell_with_snapshot(
ShellType::Zsh,
"/bin/zsh",
snapshot_path,
snapshot_path.clone(),
dir.path().to_path_buf(),
);
let command = vec![
@@ -148,9 +148,18 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_trailing_args() {
let rewritten =
maybe_wrap_shell_lc_with_snapshot(&command, &session_shell, dir.path(), &HashMap::new());
assert!(
rewritten[2]
.contains(r#"exec '/bin/bash' -c 'printf '"'"'%s %s'"'"' "$0" "$1"' 'arg0' 'arg1'"#)
assert_eq!(
rewritten,
vec![
"/bin/zsh".to_string(),
"-c".to_string(),
format!(
"if . '{}' >/dev/null 2>&1; then :; fi\n\nprintf '%s %s' \"$0\" \"$1\"",
snapshot_path.display()
),
"arg0".to_string(),
"arg1".to_string(),
]
);
}
@@ -201,7 +210,7 @@ fn maybe_wrap_shell_lc_with_snapshot_accepts_dot_alias_cwd() {
assert_eq!(rewritten[0], "/bin/zsh");
assert_eq!(rewritten[1], "-c");
assert!(rewritten[2].contains("if . '"));
assert!(rewritten[2].contains("exec '/bin/bash' -c 'echo hello'"));
assert!(rewritten[2].ends_with("\n\necho hello"));
}
#[test]