use super::CapturedSnapshot; use super::PreparedSnapshot; use super::SnapshotCaptureOptions; use super::SnapshotCredentialEnvironment; use super::SnapshotStartup; use super::prepare_snapshot_credentials; use super::snapshot_capture_script; use crate::shell_detect::ShellType; use anyhow::Context; use anyhow::Result; use pretty_assertions::assert_eq; use std::collections::HashMap; use std::os::unix::process::CommandExt; use std::process::Command; use tempfile::tempdir; fn empty_snapshot_credentials<'a>( empty: &'a HashMap, is_allowed_unset: &'a dyn Fn(&str) -> bool, ) -> SnapshotCredentialEnvironment<'a> { SnapshotCredentialEnvironment { original: empty, restored: empty, configured: empty, discovered: empty, allowed: empty, is_allowed_unset, brokered_keys: &[], brokered_alias_keys: &[], allowed_brokered_keys: &[], } } const CAPTURE_ALL: SnapshotCaptureOptions = SnapshotCaptureOptions { startup: SnapshotStartup::Interactive, declarations: true, environment: true, }; const CAPTURE_NON_INTERACTIVE: SnapshotCaptureOptions = SnapshotCaptureOptions { startup: SnapshotStartup::NonInteractive, ..CAPTURE_ALL }; fn snapshot_script(shell_type: ShellType) -> Option { snapshot_capture_script(shell_type, CAPTURE_ALL) } #[test] fn snapshot_capture_requires_marker_and_complete_records() { for captured in [ "missing header\0\0\0", "# Snapshot file\n\0", "# Snapshot file\n\0\0NAME\0export NAME=value\n", ] { assert!(CapturedSnapshot::parse(ShellType::Bash, captured.as_bytes()).is_none()); } } #[test] fn snapshot_capture_selects_declarations_and_environment() -> Result<()> { let dir = tempdir()?; for (shell, shell_type) in [ ("/bin/bash", ShellType::Bash), ("/bin/sh", ShellType::Sh), ("/bin/zsh", ShellType::Zsh), ] { if !std::path::Path::new(shell).exists() { continue; } for (declarations, environment) in [(true, false), (false, true), (true, true), (false, false)] { let script = snapshot_capture_script( shell_type, SnapshotCaptureOptions { startup: SnapshotStartup::NonInteractive, declarations, environment, }, ) .unwrap(); let output = Command::new(shell) .args(["-c", &script]) .env_clear() .env("HOME", dir.path()) .env("PATH", "/usr/bin:/bin") .env("APP_SETTING", "value") .output()?; assert!(output.status.success(), "{shell}: {output:?}"); let captured = CapturedSnapshot::parse(shell_type, &output.stdout).unwrap(); assert_eq!( ( captured .exports .iter() .any(|export| export.key == "APP_SETTING"), captured .environment .split(|byte| *byte == 0) .any(|entry| entry == b"APP_SETTING=value"), ), (declarations, environment), "{shell}" ); } } Ok(()) } #[test] fn bash_snapshot_filters_invalid_exports() -> Result<()> { let output = Command::new("/bin/bash") .arg("-c") .arg(snapshot_script(ShellType::Bash).expect("bash supports snapshots")) .env("BASH_ENV", "/dev/null") .env("VALID_NAME", "ok") .env("PWD", "/tmp/stale") .env("NEXTEST_BIN_EXE_codex-write-config-schema", "/path/to/bin") .env("BAD-NAME", "broken") .output()?; assert!(output.status.success()); let stdout = captured_script(ShellType::Bash, &String::from_utf8(output.stdout)?)?; assert!(stdout.contains("VALID_NAME")); assert!(!stdout.contains("PWD=/tmp/stale")); assert!(!stdout.contains("NEXTEST_BIN_EXE_codex-write-config-schema")); assert!(!stdout.contains("BAD-NAME")); Ok(()) } #[test] fn bash_snapshot_preserves_multiline_exports() -> Result<()> { let multiline_cert = "-----BEGIN CERTIFICATE-----\nabc\n-----END CERTIFICATE-----"; let output = Command::new("/bin/bash") .arg("-c") .arg(snapshot_script(ShellType::Bash).expect("bash supports snapshots")) .env("BASH_ENV", "/dev/null") .env("MULTILINE_CERT", multiline_cert) .output()?; assert!(output.status.success()); let stdout = captured_script(ShellType::Bash, &String::from_utf8(output.stdout)?)?; assert!( stdout.contains("MULTILINE_CERT=") || stdout.contains("MULTILINE_CERT"), "snapshot should include the multiline export name" ); let dir = tempdir()?; let snapshot_path = dir.path().join("snapshot.sh"); std::fs::write(&snapshot_path, stdout.as_bytes())?; let validate = Command::new("/bin/bash") .arg("-c") .arg("set -e; . \"$1\"") .arg("bash") .arg(&snapshot_path) .env("BASH_ENV", "/dev/null") .output()?; assert!( validate.status.success(), "snapshot validation failed: {}", String::from_utf8_lossy(&validate.stderr) ); Ok(()) } #[test] fn snapshot_environment_preserves_native_path_exports_and_shell_level() -> Result<()> { let dir = tempdir()?; for (shell, shell_type, setup) in [ ("/bin/bash", ShellType::Bash, "export -n PATH"), ("/bin/zsh", ShellType::Zsh, "typeset +x PATH"), ] { if !std::path::Path::new(shell).exists() { continue; } let run = |script: &str| { // Keep Bash's final-command exec optimization from decrementing SHLVL. Command::new(shell) .args(["-c", &format!("{setup}\n{script}\n:")]) .env_clear() .env("HOME", dir.path()) .env("PATH", "/usr/bin:/bin") .env("SHLVL", "3") .output() }; let native = run("/usr/bin/env -0")?; let script = snapshot_capture_script( shell_type, SnapshotCaptureOptions { startup: SnapshotStartup::NonInteractive, ..CAPTURE_ALL }, ) .unwrap(); let output = run(&script)?; assert!(native.status.success() && output.status.success()); let captured = CapturedSnapshot::parse(shell_type, &output.stdout).unwrap(); let metadata = |environment: &[u8]| { environment .split(|byte| *byte == 0) .filter(|entry| entry.starts_with(b"PATH=") || entry.starts_with(b"SHLVL=")) .map(<[u8]>::to_vec) .collect::>() }; assert_eq!( metadata(captured.environment), metadata(&native.stdout), "{shell}" ); } Ok(()) } #[test] fn sh_declarations_preserve_path_export_state() -> Result<()> { let dir = tempdir()?; let snapshot_path = dir.path().join("snapshot.sh"); let script = snapshot_capture_script( ShellType::Sh, SnapshotCaptureOptions { startup: SnapshotStartup::NonInteractive, ..CAPTURE_ALL }, ) .expect("sh supports snapshots"); for shell in [ "/bin/sh", #[cfg(target_os = "macos")] "/bin/dash", ] { if !std::path::Path::new(shell).exists() { continue; } for (setup, expected) in [ ("PATH=/enterprise/bin; export PATH", "x:/enterprise/bin"), ("PATH=; export PATH", "x:"), ("unset PATH; export PATH", ":"), ("unset PATH; PATH=/enterprise/bin", ":"), ] { let captured = Command::new(shell) .args(["-uc", &format!("{setup}\n{script}")]) .env_clear() .output()?; assert!(captured.status.success(), "capture failed: {captured:?}"); std::fs::write( &snapshot_path, captured_script(ShellType::Sh, &String::from_utf8(captured.stdout)?)?, )?; let restored = Command::new(shell) .args([ "-c", "unset PATH; . \"$1\"; printf '%s:%s' \"${PATH+x}\" \"${PATH-}\"", "snapshot", ]) .arg(&snapshot_path) .env_clear() .output()?; assert!(restored.status.success(), "replay failed: {restored:?}"); assert_eq!(restored.stdout, expected.as_bytes(), "{setup}"); } } Ok(()) } #[test] fn posix_startup_path_expansion_preserves_supported_forms() -> Result<()> { let script = format!( "{}\n__codex_snapshot_expand_env \"$ENV\"", super::posix_env_path_expansion_function() ); let output = Command::new("/bin/sh") .arg("-uc") .arg(&script) .env("ENV", "$MISSING") .output()?; assert!(output.status.success(), "snapshot failed: {output:?}"); assert_eq!(output.stdout, b"$MISSING"); let dir = tempdir()?; let startup = dir.path().join("startup.sh"); for (env_value, expansion_env) in [ ( startup.display().to_string(), Vec::<(String, String)>::new(), ), ( "$STARTUP_FILE".to_string(), vec![("STARTUP_FILE".to_string(), startup.display().to_string())], ), ( "${STARTUP_FILE}".to_string(), vec![("STARTUP_FILE".to_string(), startup.display().to_string())], ), ( "${STARTUP_DIR}/startup.sh".to_string(), vec![("STARTUP_DIR".to_string(), dir.path().display().to_string())], ), ( "${PATH%%:*}/startup.sh".to_string(), vec![( "PATH".to_string(), format!("{}:/usr/bin:/bin", dir.path().display()), )], ), ( "$PATH/startup.sh".to_string(), vec![("PATH".to_string(), dir.path().display().to_string())], ), ( "${PATH}/startup.sh".to_string(), vec![("PATH".to_string(), dir.path().display().to_string())], ), ( format!("$PATH{}", startup.display()), vec![("PATH".to_string(), String::new())], ), ( format!("${{PATH}}{}", startup.display()), vec![("PATH".to_string(), String::new())], ), ( "~/startup.sh".to_string(), vec![("HOME".to_string(), dir.path().display().to_string())], ), ] { let output = Command::new("/bin/sh") .arg("-uc") .arg(&script) .env("ENV", &env_value) .envs(expansion_env) .output()?; assert!(output.status.success(), "snapshot failed: {output:?}"); assert_eq!( String::from_utf8(output.stdout)?, startup.display().to_string(), "ENV={env_value}" ); } let default_startup_dir = dir.path().join(".config"); for xdg_config_home in [ None, Some(std::ffi::OsStr::new("")), Some(default_startup_dir.as_os_str()), ] { let mut command = Command::new("/bin/sh"); command .arg("-uc") .arg(&script) .env("HOME", dir.path()) .env("ENV", "${XDG_CONFIG_HOME:-$HOME/.config}/startup.sh"); if let Some(xdg_config_home) = xdg_config_home { command.env("XDG_CONFIG_HOME", xdg_config_home); } else { command.env_remove("XDG_CONFIG_HOME"); } let output = command.output()?; assert!(output.status.success(), "snapshot failed: {output:?}"); assert_eq!( String::from_utf8(output.stdout)?, default_startup_dir.join("startup.sh").display().to_string() ); } let injected_marker = dir.path().join("env-injected"); let injected_env = format!( "\"; touch '{}'; __codex_env_file=\"", injected_marker.display() ); let output = Command::new("/bin/sh") .arg("-uc") .arg(&script) .env("ENV", injected_env) .output()?; assert!(output.status.success(), "snapshot failed: {output:?}"); assert!( !injected_marker.exists(), "snapshot interpreted ENV as shell source" ); Ok(()) } #[test] fn brokered_snapshots_filter_complete_multiline_exports() -> Result<()> { let dir = tempdir()?; for (shell, shell_type) in [ ("/bin/bash", ShellType::Bash), ("/bin/sh", ShellType::Sh), ("/bin/dash", ShellType::Sh), ("/bin/zsh", ShellType::Zsh), ] { if !std::path::Path::new(shell).exists() { continue; } for value in [ "before\nexport NOT_A_REAL_VARIABLE=value\nafter", "before'\ndeclare -x NOT_A_REAL_VARIABLE=\"quoted\"\ntypeset OTHER=value\nafter\\", "before\t\n# exports 99\nexport NOT_A_REAL_VARIABLE=value\nafter", "certificate\nwith trailing newlines\n\n", "before\nexport NOT_A_REAL_VARIABLE\nafter", "before\nexport name=not_the_actual_value\nafter", ] { let original = HashMap::from([ ("APP_SCRIPT".to_string(), value.to_string()), ("EXCLUDED_SCRIPT".to_string(), value.to_string()), ("ZZZ_AFTER".to_string(), "kept".to_string()), ("name".to_string(), "original name".to_string()), ("value".to_string(), "original value".to_string()), ("escaped".to_string(), "original escaped".to_string()), ]); let mut allowed = original.clone(); allowed.remove("EXCLUDED_SCRIPT"); let function_value = "before\n# exports 1\nexport NOT_A_REAL_VARIABLE=value\nafter"; let capture_script = snapshot_capture_script( shell_type, SnapshotCaptureOptions { startup: SnapshotStartup::NonInteractive, ..CAPTURE_ALL }, ) .expect("shell supports snapshots"); let capture = if matches!(shell_type, ShellType::Sh) { capture_script } else { format!("emit_source() {{ cat <<'EOF'\n{function_value}\nEOF\n}}\n{capture_script}") }; let capture = if matches!(shell_type, ShellType::Sh | ShellType::Zsh) { format!("export APP_SETTING EXCLUDED_SETTING\n{capture}") } else { capture }; let captured = Command::new(shell) .args(["-c", &capture]) .env_clear() .envs(&original) .env("PATH", "/usr/bin:/bin") .output()?; assert!(captured.status.success(), "{shell}: {captured:?}"); let mut snapshot = String::from_utf8(captured.stdout)?; let records = CapturedSnapshot::parse(shell_type, snapshot.as_bytes()).unwrap(); assert_eq!( records .exports .iter() .filter(|export| export.key == "name") .count(), 1, "{shell}: duplicate export record" ); if matches!(shell_type, ShellType::Sh) { let raw_path = dir.path().join("raw-snapshot.sh"); std::fs::write(&raw_path, captured_script(shell_type, &snapshot)?)?; let replayed = Command::new(shell) .args(["-c", ". \"$1\"; printf '%s\\n' \"${APP_SETTING-unset}\"; APP_SETTING=production; /usr/bin/printenv APP_SETTING; NOT_A_REAL_VARIABLE=unexpected; /usr/bin/printenv NOT_A_REAL_VARIABLE", "snapshot"]) .arg(&raw_path) .env_clear() .output()?; assert_eq!( replayed.stdout, b"unset\nproduction\n", "{shell}: {snapshot}" ); assert_eq!(replayed.status.code(), Some(1)); } rewrite_snapshot_credentials( shell_type, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &original, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|key| key == "APP_SETTING", brokered_keys: &[], brokered_alias_keys: &[], allowed_brokered_keys: &[], }, |_| true, ); let path = dir.path().join("snapshot.sh"); std::fs::write(&path, snapshot)?; let replayed = Command::new(shell) .args(["-c", ". \"$1\"; printf '%s\\0%s\\0%s\\0%s\\0%s\\0%s' \"$APP_SCRIPT\" \"${EXCLUDED_SCRIPT-unset}\" \"$ZZZ_AFTER\" \"$name\" \"$value\" \"$escaped\"; if command -v emit_source >/dev/null; then emit_source; fi; APP_SETTING=production; EXCLUDED_SETTING=denied; /usr/bin/printenv APP_SETTING; /usr/bin/printenv EXCLUDED_SETTING || :", "snapshot"]) .arg(&path) .env_clear() .output()?; assert!(replayed.status.success(), "{shell}: {replayed:?}"); let function_value = if matches!(shell_type, ShellType::Sh) { String::new() } else { format!("{function_value}\n") }; let exported_setting = if matches!(shell_type, ShellType::Sh | ShellType::Zsh) { "production\n" } else { "" }; assert_eq!( replayed.stdout, format!("{value}\0unset\0kept\0original name\0original value\0original escaped{function_value}{exported_setting}").as_bytes(), "{shell}" ); } } Ok(()) } #[test] fn brokered_zsh_snapshot_preserves_tied_credential_aliases() -> Result<()> { if !std::path::Path::new("/bin/zsh").exists() { return Ok(()); } let dir = tempdir()?; let real = "password$abcdefghijklmno"; let dummy = "dummy-0123456789abcdef"; let last = "ordinary'quote\nnext"; let brokered_keys = vec!["VENDOR_PASSWORD".to_string()]; for separator in [":", "|", "'", "\t", "$", "_", "x"] { let appended = format!("ordinary{separator}other"); for (rcquotes, captured_credential) in [ (false, real), (true, real), (false, "captured$0123456789abcdef"), (true, "pin_ab$cd"), ] { let script = snapshot_capture_script(ShellType::Zsh, CAPTURE_NON_INTERACTIVE).unwrap(); let setup = format!( "{}\nexport -UT SERVICE_URLS service_urls=( \"https://user:$VENDOR_PASSWORD@example.invalid/path\" '' {} ) {}\nservice_urls+=({})\n{script}", if rcquotes { "setopt rcquotes rcexpandparam" } else { ":" }, shlex::try_quote(last)?, shlex::try_quote(separator)?, shlex::try_quote(&appended)?, ); let original = HashMap::from([ ( "VENDOR_PASSWORD".to_string(), captured_credential.to_string(), ), ( "SERVICE_URLS".to_string(), [ format!("https://user:{captured_credential}@example.invalid/path"), String::new(), last.to_string(), appended.clone(), ] .join(separator), ), ]); let captured = Command::new("/bin/zsh") .args(["-fc", &setup]) .env_clear() .env("VENDOR_PASSWORD", captured_credential) .env("PATH", "/usr/bin:/bin") .output()?; assert!(captured.status.success(), "{captured:?}"); let mut snapshot = String::from_utf8(captured.stdout)?; let allowed = original .iter() .map(|(key, value)| (key.clone(), value.replace(captured_credential, dummy))) .collect::>(); let restored = original .iter() .map(|(key, value)| (key.clone(), value.replace(captured_credential, real))) .collect::>(); let rewritten = rewrite_snapshot_credentials( ShellType::Zsh, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &restored, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|_| true, brokered_keys: &brokered_keys, brokered_alias_keys: &[], allowed_brokered_keys: &brokered_keys, }, |text| { *text = text .replace(real, dummy) .replace(captured_credential, dummy); true }, ) .expect("tied export can be brokered"); assert_eq!( rewritten.aliases, HashMap::from([("SERVICE_URLS".to_string(), allowed["SERVICE_URLS"].clone())]) ); assert!(!snapshot.contains(real)); let path = dir.path().join("snapshot.sh"); std::fs::write(&path, &snapshot)?; for live in [dummy, real] { let replayed = Command::new("/bin/zsh") .args(["-fc", "set -e; . \"$1\"; printf '%s\\0' \"$SERVICE_URLS\" \"${service_urls[@]}\" \"$options[rcquotes]\"", "snapshot"]) .arg(&path) .env_clear() .env("VENDOR_PASSWORD", live) .output()?; assert!(replayed.status.success(), "{replayed:?}"); let scalar = original["SERVICE_URLS"].replace(captured_credential, live); let mut array = vec![ format!("https://user:{live}@example.invalid/path"), String::new(), last.to_string(), ]; array.push(appended.clone()); assert_eq!( replayed.stdout, format!( "{scalar}\0{}\0{}\0", array.join("\0"), if rcquotes { "on" } else { "off" } ) .into_bytes() ); } } } for unique in [false, true] { for separator in [":", "|", "'", "\t"] { let flags = if unique { "-UT" } else { "-T" }; let separator = shlex::try_quote(separator)?; let binding = format!("export {flags} SERVICE_URLS service_urls {separator}"); let script = snapshot_capture_script(ShellType::Zsh, CAPTURE_NON_INTERACTIVE).unwrap(); let captured = Command::new("/bin/zsh") .args([ "-fc", &format!( "export {flags} SERVICE_URLS service_urls=( '' \"$VENDOR_PASSWORD\" '' plain plain '' ) {separator}\nunset VENDOR_PASSWORD\n{script}" ), ]) .env_clear() .env("VENDOR_PASSWORD", real) .env("PATH", "/usr/bin:/bin") .output()?; assert!(captured.status.success(), "{captured:?}"); let mut snapshot = String::from_utf8(captured.stdout)?; let original = CapturedSnapshot::parse(ShellType::Zsh, snapshot.as_bytes()) .unwrap() .environment .split(|byte| *byte == 0) .filter_map(|entry| std::str::from_utf8(entry).ok()?.split_once('=')) .map(|(key, value)| (key.to_string(), value.to_string())) .collect::>(); assert!(!original.contains_key("VENDOR_PASSWORD")); let allowed = original .iter() .map(|(key, value)| (key.clone(), value.replace(real, dummy))) .collect::>(); let alias_keys = vec!["SERVICE_URLS".to_string()]; let rewritten = rewrite_snapshot_credentials( ShellType::Zsh, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &original, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|_| true, brokered_keys: &[], brokered_alias_keys: &alias_keys, allowed_brokered_keys: &[], }, |text| { *text = text.replace(real, dummy); true }, ) .expect("source-less tied alias can be brokered"); assert_eq!(rewritten.aliases, HashMap::new()); assert!(!snapshot.contains(real)); assert!(!snapshot.contains(dummy)); let path = dir.path().join("source-less-snapshot.sh"); std::fs::write(&path, &snapshot)?; let inspect = "printf '%s\\0' \"${SERVICE_URLS-unset}\" \"${service_urls[@]}\" \"${(t)service_urls}\" \"${VENDOR_PASSWORD-unset}\"; if [ -n \"${SERVICE_URLS+x}\" ]; then SERVICE_URLS=$UPDATE; printf '%s\\0' \"$SERVICE_URLS\" \"${service_urls[@]}\"; unset SERVICE_URLS; fi; printf '%s:%s' \"${+SERVICE_URLS}\" \"${+service_urls}\""; for live in [Some(allowed["SERVICE_URLS"].as_str()), Some(""), None] { for source in [None, Some("different-live-source")] { let mut outputs = Vec::new(); for script in [ format!("set -e; . \"$1\"; {inspect}"), format!( "set -e; if [ -n \"${{SERVICE_URLS+x}}\" ]; then {binding}; fi; {inspect}" ), ] { let mut command = Command::new("/bin/zsh"); command .args(["-fc", &script, "snapshot"]) .arg(&path) .env_clear() .env( "UPDATE", allowed["SERVICE_URLS"].replace(dummy, "new-live-dummy"), ); if let Some(live) = live { command.env("SERVICE_URLS", live); } if let Some(source) = source { command.env("VENDOR_PASSWORD", source); } let output = command.output()?; assert!(output.status.success(), "{script}: {output:?}"); outputs.push(String::from_utf8(output.stdout)?); } assert_eq!(outputs[0], outputs[1], "{binding}, {live:?}, {source:?}"); } } } } Ok(()) } #[test] fn brokered_snapshot_rejects_credentials_crossing_array_elements() { let real = "password$abcdefghijklmno"; let dummy = "dummy-0123456789abcdef"; let keys = vec!["VENDOR_PASSWORD".to_string()]; let restored = HashMap::from([(keys[0].clone(), real.to_string())]); for (captured_value, allowed_value) in [(real, dummy), (real, real), (dummy, dummy)] { let original = HashMap::from([ (keys[0].clone(), captured_value.to_string()), ("AUTH_HEADER".to_string(), captured_value.to_string()), ]); let allowed = HashMap::from([ (keys[0].clone(), allowed_value.to_string()), ("AUTH_HEADER".to_string(), allowed_value.to_string()), ]); let (first, last) = captured_value.split_at(/*mid*/ 10); let data = format!( "# Snapshot file\n\0\0AUTH_HEADER\0typeset -xT AUTH_HEADER header=( '{first}' '{last}' ) ''\n\0\0" ); let capture = CapturedSnapshot::parse(ShellType::Zsh, data.as_bytes()).unwrap(); let prepared = prepare_snapshot_credentials( &capture, SnapshotCredentialEnvironment { original: &original, restored: &restored, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|_| true, brokered_keys: &keys, brokered_alias_keys: &[], allowed_brokered_keys: &keys, }, |text| { *text = text.replace(real, allowed_value); true }, ); assert!(prepared.is_none(), "{captured_value}, {allowed_value}"); } } #[test] fn zsh_literal_bytes_match_native_modifiers() -> Result<()> { if !std::path::Path::new("/bin/zsh").exists() { return Ok(()); } for word in [ r"$'\M-C\M-)'", r"$'\xC3'$'\M-)'", r"$'\M-\C-?'", r"$'\C-\M-?'", r"$'\C-\xC3'", r"$'\M\x41'", r"$'\M-\u0041X'", r"$'\M-\q'", ] { let script = format!("printf '%s' {word}"); let native = Command::new("/bin/zsh") .args(["-fc", &script]) .env_clear() .output()?; assert!(native.status.success(), "{word}: {native:?}"); let tree = crate::bash::try_parse_shell(&script).unwrap(); let node = tree.root_node().named_child(0).unwrap(); let mut cursor = node.walk(); let argument = node .children_by_field_name("argument", &mut cursor) .last() .unwrap(); let decoded = super::literals::literal_word_bytes( argument, &script, ShellType::Zsh, super::literals::Quoting::Posix, ) .unwrap(); assert_eq!(decoded, native.stdout, "{word}"); } Ok(()) } #[test] fn brokered_snapshots_check_complete_alias_literals() -> Result<()> { let real = "token_ab'cdefghijklmnopqrstuvwx"; let original = HashMap::from([("VENDOR_PASSWORD".to_string(), real.to_string())]); let allowed = HashMap::from([( "VENDOR_PASSWORD".to_string(), "token_dummy_abcdefghijklmnopqr".to_string(), )]); let brokered_keys = vec!["VENDOR_PASSWORD".to_string()]; let controls = (1u8..=31) .map(char::from) .chain(['\u{7f}', '\u{e9}']) .collect::(); for (shell, aliases) in [("/bin/bash", "alias -p"), ("/bin/zsh", "alias -L")] { let shell_type = crate::shell_detect::detect_shell_type(shell).unwrap(); if !std::path::Path::new(shell).exists() { continue; } for (value, accepted) in [ (format!("printf '%s' \"{real}\""), false), (format!("printf '%s' \"{real}\"\n:"), false), ( "printf %s token_ab\\'cdefghijklmnopqrstuvwx\n:".to_string(), false, ), ( "printf %s 'token_ab'\\''cdefghijklmnopqrstuvwx'\n:".to_string(), false, ), ( "printf '%s' \"ordinary'quoted\"\n# tab\tbell\u{7}escape\u{1b}backslash\\\n:" .to_string(), true, ), (format!("printf '%s' \"{controls}\""), true), ] { let output = Command::new(shell) .args(["-c", &format!("alias helper=\"$ALIAS_VALUE\"; {aliases}")]) .env_clear() .env("ALIAS_VALUE", &value) .output()?; assert!(output.status.success(), "{shell}: {output:?}"); let mut snapshot = format!( "# aliases\n{}\n# exports 0\n", String::from_utf8(output.stdout)? ); let before = snapshot.clone(); let rewritten = rewrite_snapshot_credentials( shell_type, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &original, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|_| false, brokered_keys: &brokered_keys, brokered_alias_keys: &[], allowed_brokered_keys: &brokered_keys, }, |text| { *text = text.replace(real, &allowed["VENDOR_PASSWORD"]); true }, ); assert_eq!(rewritten.is_some(), accepted, "{shell}: {before}"); if accepted { assert_eq!(snapshot, before); let expected = if shell_type == ShellType::Zsh { let replay = Command::new(shell) .args(["-fc", &format!("{snapshot}\nprint -rn -- $aliases[helper]")]) .env_clear() .output()?; assert!(replay.status.success(), "{replay:?}"); String::from_utf8(replay.stdout)? } else { value.clone() }; assert!( super::literals::literal_words(&snapshot, shell_type) .unwrap() .contains(&format!("helper={expected}")), "{shell}: {value:?}; {snapshot}" ); } } } Ok(()) } #[test] fn brokered_snapshots_check_prefixed_alias_declarations() -> Result<()> { let empty = HashMap::new(); let real = "token_ab'cd\"efghijklmnopqrstuvwx"; let body = format!("printf '%s' {}", shlex::try_quote(real)?); for (shell, shell_type, setup) in [ ("/bin/bash", ShellType::Bash, "shopt -s expand_aliases"), ("/bin/zsh", ShellType::Zsh, "setopt posixbuiltins"), ] { if !std::path::Path::new(shell).exists() { continue; } for (prefix, creates_alias, name) in [ ("alias", true), ("builtin alias", true), ("builtin builtin alias", true), ("time builtin alias", true), ("command alias", true), ("command -p -- alias", true), ("command -pp alias", true), ("command builtin alias", true), ("builtin command -p alias", true), ("command -v alias", false), ("command -pV alias", false), ] .into_iter() .chain((shell_type == ShellType::Bash).then_some(("builtin -- alias", true))) .chain((shell_type == ShellType::Bash).then_some(("time -p builtin alias", true))) .chain( (shell_type == ShellType::Zsh) .then_some([ ("noglob builtin alias", true), ("builtin - alias", true), ("- builtin alias", true), ]) .into_iter() .flatten(), ) .flat_map(|(prefix, creates_alias)| { ["captured", "\"$name\""].map(|name| (prefix, creates_alias, name)) }) { let capture = format!( "helper() {{ local name=captured; {prefix} {name}={}; }}\ntypeset -f helper", shlex::try_quote(&body)? ); let output = Command::new(shell) .args(["-fc", &capture]) .env_clear() .output()?; assert!(output.status.success(), "{shell}: {output:?}"); let original = String::from_utf8(output.stdout)?; assert!(!original.contains(real), "{shell}: {original}"); for contains_credential in [false, true] { let mut snapshot = original.clone(); let result = rewrite_snapshot_credentials( shell_type, &mut snapshot, empty_snapshot_credentials(&empty, &|_| false), |text| { if contains_credential { *text = text.replace(real, "dummy"); } true }, ); assert_eq!( result.is_some(), !contains_credential || !creates_alias, "{shell}, {prefix}: {snapshot}" ); assert_eq!(snapshot, original); if !contains_credential && creates_alias { let replay = Command::new(shell) .args([ "-fc", &format!("{setup}\n{snapshot}\nhelper\neval captured"), ]) .env_clear() .output()?; assert!(replay.status.success(), "{shell}, {prefix}: {replay:?}"); assert_eq!(String::from_utf8(replay.stdout)?, real); } } } } Ok(()) } #[test] fn brokered_snapshots_check_array_initializers_and_binary_literals() { let empty = HashMap::new(); let real = "password$abcdefghijklmno"; for (shell_type, original, has_credential) in [ ( ShellType::Bash, r#"declare -ax API_HEADERS='([0]="Bearer password\$abcdefghijklmno")'"#, ), ( ShellType::Bash, r#"builtin declare -ax API_HEADERS='([0]="Bearer password\$abcdefghijklmno")'"#, ), ( ShellType::Bash, r#"command -p typeset -ax API_HEADERS='([0]="Bearer password\$abcdefghijklmno")'"#, ), ( ShellType::Bash, r#"declare -Ax API_HEADERS=([auth]="Bearer password\$abcdefghijklmno")"#, ), ( ShellType::Bash, r#"helper() { local -a headers; local headers='([0]="Bearer password\$abcdefghijklmno")'; }"#, ), ( ShellType::Bash, r#"declare -a headers; builtin declare headers='([0]="Bearer password\$abcdefghijklmno")'"#, ), ( ShellType::Bash, r#"declare -a headers; command -p typeset headers='([0]="Bearer password\$abcdefghijklmno")'"#, ), ( ShellType::Bash, r#"declare SCALAR_TEMPLATE='([0]="Bearer password\$abcdefghijklmno")'"#, ), ( ShellType::Bash, r#"helper() { local name=headers; local -a headers; local "${name}"='([0]="Bearer password\$abcdefghijklmno")'; }"#, ), ( ShellType::Bash, r#"helper() { local -a headers; local headers='([0]="Bearer password\$abcdefghijklmno" [1]="'"$OTHER"'")'; }"#, ), ( ShellType::Bash, r#"helper() { local -a headers; local headers=$PREFIX'([0]="Bearer password\$abcdefghijklmno")'$SUFFIX; }"#, ), ( ShellType::Zsh, r#"helper() { printf '%s' $'\x89password\x24abcdefghijklmno\xff'; }"#, ), ] .into_iter() .map(|(shell_type, original)| (shell_type, original, true)) .chain( [ r#"local TEMPLATE="(\$'\\xZZ')""#, r#"local TEMPLATE="(\$'\\uD800')""#, r#"local TEMPLATE="(\$'\\UFFFFFFFF')""#, r#"local TEMPLATE="(\$'\\c')""#, r#"local TEMPLATE="(\$'\\')""#, "local TEMPLATE=\"(\\$'\\\\c\u{e9}')\"", r#"local TEMPLATE="(\$'incomplete)""#, r#"local -a headers; local headers='([0]="Bearer '$CREDENTIAL'")'"#, ] .map(|original| (ShellType::Bash, original, false)), ) { for contains_credential in [false, true] { let mut snapshot = original.to_string(); let result = rewrite_snapshot_credentials( shell_type, &mut snapshot, empty_snapshot_credentials(&empty, &|_| true), |text| { if contains_credential { *text = text.replace(real, "dummy"); } true }, ); assert_eq!( result.is_some(), !contains_credential || !has_credential, "{original}" ); assert_eq!(snapshot, original); } } } #[test] fn brokered_sh_snapshots_inspect_the_captured_shell_dialect() -> Result<()> { let empty = HashMap::new(); let real = "password$abcdefghijklmno"; let script = snapshot_capture_script(ShellType::Sh, CAPTURE_NON_INTERACTIVE).unwrap(); for (shell, bash_backed) in [("/bin/bash", true), ("/bin/dash", false)] { if !std::path::Path::new(shell).exists() { continue; } for (setup, contains_credential) in [ ( r#"helper() { local -a headers; local headers='([0]="Bearer password\$abcdefghijklmno")'; }"#, true, ), ( r#"helper() { time -p builtin alias captured='printf "%s" "password\$abcdefghijklmno"'; }"#, true, ), ( r#"helper() { local name=captured; alias "$name"='printf "%s" "password\$abcdefghijklmno"'; }"#, true, ), ( r#"export TEMPLATE='([0]="Bearer password\$abcdefghijklmno")'"#, true, ), (r#"export TEMPLATE="(\$'incomplete)""#, false), ( r#"helper() { local -a headers; local headers='([0]="ordinary\$value")'; }; export TEMPLATE=ordinary"#, false, ), ] { let capture = Command::new(shell) .arg0("sh") .args(["-c", &format!("{setup}\n{script}")]) .env_clear() .env("PATH", "/usr/bin:/bin") .output()?; assert!(capture.status.success(), "{shell}: {capture:?}"); let original = String::from_utf8(capture.stdout)?; assert_eq!( original.starts_with(super::BASH_SH_SNAPSHOT_HEADER), bash_backed ); let mut snapshot = original.clone(); let result = rewrite_snapshot_credentials( ShellType::Sh, &mut snapshot, empty_snapshot_credentials(&empty, &|_| true), |text| { *text = text.replace(real, "dummy"); true }, ); assert_eq!( result.is_some(), !bash_backed || !contains_credential, "{shell}: {setup}" ); assert_eq!( snapshot, if result.is_some() { captured_script(ShellType::Sh, &original)? } else { original } ); if result.is_some() { let replay = Command::new(shell) .arg0("sh") .args(["-c", &format!("{snapshot}\nprintf '%s' \"${{TEMPLATE-}}\"")]) .env_clear() .output()?; let control = Command::new(shell) .arg0("sh") .args(["-c", &format!("{setup}\nprintf '%s' \"${{TEMPLATE-}}\"")]) .env_clear() .output()?; assert!(replay.status.success(), "{shell}: {replay:?}"); assert!(control.status.success(), "{shell}: {control:?}"); assert_eq!(replay.stdout, control.stdout); } } } Ok(()) } #[test] fn brokered_snapshots_check_native_quoting() -> Result<()> { let empty = HashMap::new(); for (shell, shell_type, aliases) in [ ("/bin/bash", ShellType::Bash, "alias -p"), ("/bin/zsh", ShellType::Zsh, "alias -L"), ] { if !std::path::Path::new(shell).exists() { continue; } let mut expressions = vec![ r#""`printf '%s' 'password\$abcdefghijklmno'`""#, r#"`printf '%s' 'password\$abcdefghijklmno'`"#, r#""`printf '%s' \`printf '%s' 'password\\\$abcdefghijklmno'\``""#, r#""${EMPTY-`printf '%s' 'password\"abcdefghijklmno'`}""#, r#"$"token_ab'cdefghijklmnopqrstuvwx""#, r#"prefix$"token_ab'cdefghijklmnopqrstuvwx""#, r#"$"token_ab'cdefghijklmnopqrstuvwx""${EMPTY-}""#, r#"$'token_\c\\abcdefghijklmnopqrstuvwx'"#, r#""$(printf '%s' $'token_\c\\abcdefghijklmnopqrstuvwx')""#, r#"$'token_\C-Aabcdefghijklmnopqrstuvwx'"#, r#"$'token_\C-\x41abcdefghijklmnopqrstuvwx'"#, r#"$'token_\C-\101abcdefghijklmnopqrstuvwx'"#, r#"$'token_\C-\u0041abcdefghijklmnopqrstuvwx'"#, r#"$'token_\C\101abcdefghijklmnopqrstuvwx'"#, ]; let mut array_expressions = Vec::new(); if shell_type == ShellType::Bash { expressions.extend([ r#""$(array_value() { local -a headers; local headers='([0]="password\$abcdefghijklmno")'; printf '%s' "${headers[0]}"; }; array_value)""#, r#""$(declare -a headers; builtin declare headers='([0]="password\$abcdefghijklmno")'; printf '%s' "${headers[0]}")""#, r#""$(declare -a headers; command -p typeset headers='([0]="password\$abcdefghijklmno")'; printf '%s' "${headers[0]}")""#, r#""$(array_value() { local name=headers; local -a headers; local "${name}"='([0]="password\$abcdefghijklmno")'; printf '%s' "${headers[0]}"; }; array_value)""#, r#""$(array_value() { local -a headers; local headers='([0]="password\$abcdefghijklmno" [1]="'"$OTHER"'")'; printf '%s' "${headers[0]}"; }; array_value)""#, r#""$(array_value() { local -a headers; local headers=$PREFIX'([0]="password\$abcdefghijklmno")'$SUFFIX; printf '%s' "${headers[0]}"; }; array_value)""#, ]); for escape in [r"\xZZ", r"\uD800", r"\UFFFFFFFF", r"\c"] { let initializer = format!(r#"([0]=$'{escape}' [1]="password\$abcdefghijklmno")"#); let initializer = shlex::try_quote(&initializer)?; array_expressions.push(format!( "\"$(declare -a headers; declare headers={initializer}; printf '%s' \"${{headers[1]}}\")\"" )); } } else if shell_type == ShellType::Zsh { expressions.extend([ "password_a$'\\xc3'$'\\xa9'bcdefghijklmno\"${EMPTY-}\"", "password_a$'\\M-C'$'\\M-)'bcdefghijklmno\"${EMPTY-}\"", "password_a$'\\xc3'\\\n$'\\xa9'bcdefghijklmno\"${EMPTY-}\"", "password\\\n\\$abcdefghijklmno", "pass\\\nword\\\n\\\n\\$abcdefghijklmno", "'password'\\\n'$abcdefghijklmno'", "\"password\"\\\n'$abcdefghijklmno'", "$'pass'\"word\"\\\n'$abcdefghijklmno'", ]); } for expression in expressions .into_iter() .map(str::to_string) .chain(array_expressions) { let body = format!("printf '%s' {expression}"); let native = Command::new(shell) .args(["-fc", &body]) .env_clear() .output()?; assert!(native.status.success(), "{shell}, {expression}: {native:?}"); let real = String::from_utf8(native.stdout)?; assert!(real.len() >= 16, "{shell}, {expression}: {real:?}"); let mut captures = vec![ format!("helper() {{ {body}; }}\ntypeset -f helper"), format!("alias helper=\"$ALIAS_VALUE\"\n{aliases}"), ]; if shell_type == ShellType::Zsh { captures.push(format!("alias -g helper=\"$ALIAS_VALUE\"\n{aliases}")); let script = snapshot_capture_script(shell_type, CAPTURE_NON_INTERACTIVE) .expect("zsh supports snapshots"); captures.push(format!( "quoted() {{ cat <<'EOF'\n\" ${{(@f)EMPTY:-password\\$abcdefghijklmno}}\nEOF\n}}\nhelper() {{ {body}; }}\n{script}" )); } for capture in captures { let output = Command::new(shell) .args(["-fc", &capture]) .env_clear() .env("ALIAS_VALUE", &body) .output()?; assert!(output.status.success(), "{shell}: {output:?}"); let captured = String::from_utf8(output.stdout)?; let original = if captured.contains('\0') { let capture = CapturedSnapshot::parse(shell_type, captured.as_bytes()).unwrap(); format!("{}{}", capture.state, capture.aliases) } else { captured }; for contains_credential in [false, true] { let mut snapshot = original.clone(); let result = rewrite_snapshot_credentials( shell_type, &mut snapshot, empty_snapshot_credentials(&empty, &|_| false), |text| { if contains_credential { *text = text.replace(&real, "dummy"); } true }, ); assert_eq!( result.is_some(), !contains_credential, "{shell}, {expression}: {snapshot}" ); assert_eq!(snapshot, original); if !contains_credential { let setup = if shell_type == ShellType::Bash { "shopt -s expand_aliases\n" } else { "" }; let replay = Command::new(shell) .args(["-fc", &format!("{setup}{snapshot}\neval helper")]) .env_clear() .output()?; assert!( replay.status.success(), "{shell}, {expression}: {snapshot}\n{replay:?}" ); assert_eq!(String::from_utf8(replay.stdout)?, real); } } } } } Ok(()) } #[test] fn zsh_snapshot_checks_functions_before_restoring_quote_options() -> Result<()> { if !std::path::Path::new("/bin/zsh").exists() { return Ok(()); } let dir = tempdir()?; let real = "ghp_abcdefghijklmnopqrstuvwxyz0123456789"; let original = HashMap::from([("GH_TOKEN".to_string(), real.to_string())]); let allowed = HashMap::from([("GH_TOKEN".to_string(), "dummy-github-token".to_string())]); let keys = vec!["GH_TOKEN".to_string()]; let script = snapshot_capture_script(ShellType::Zsh, CAPTURE_NON_INTERACTIVE) .expect("zsh supports snapshots"); for option in ["setopt rcquotes", "unsetopt rcquotes"] { for (value, accepted) in [(real, false), ("ordinaryvalue", true)] { let (first, second) = value.split_at(value.len() / 2); let startup = dir.path().join("startup.sh"); std::fs::write( &startup, format!("helper() {{ printf '%s' '{first}''{second}'; }}\n{option}\n"), )?; let output = Command::new("/bin/zsh") .args(["-fc", &format!(". \"$1\"\n{script}"), "snapshot"]) .arg(startup) .env_clear() .envs(&original) .env("HOME", dir.path()) .output()?; assert!(output.status.success(), "{output:?}"); let mut snapshot = String::from_utf8(output.stdout)?; let rewritten = rewrite_snapshot_credentials( ShellType::Zsh, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &original, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|_| false, brokered_keys: &keys, brokered_alias_keys: &[], allowed_brokered_keys: &keys, }, |text| { *text = text.replace(real, &allowed["GH_TOKEN"]); true }, ); assert_eq!(rewritten.is_some(), accepted, "{option}: {snapshot}"); if accepted { let path = dir.path().join("snapshot.sh"); std::fs::write(&path, &snapshot)?; let replay = Command::new("/bin/zsh") .args(["-fc", ". \"$1\"; helper", "snapshot"]) .arg(path) .env_clear() .envs(&allowed) .output()?; assert!(replay.status.success(), "{replay:?}"); assert_eq!(String::from_utf8(replay.stdout)?, value); } } } Ok(()) } #[test] fn zsh_snapshot_checks_deferred_quote_modes() -> Result<()> { if !std::path::Path::new("/bin/zsh").exists() { return Ok(()); } let dir = tempdir()?; let script = snapshot_capture_script(ShellType::Zsh, CAPTURE_NON_INTERACTIVE) .expect("zsh supports snapshots"); for (real, startup) in [ ( "token_ab'cdefghijklmnopqrstuvwx", "helper() { printf '%s' \"$(printf '%s' 'token_ab''cdefghijklmnopqrstuvwx')\"; }\nsetopt rcquotes\n", ), ( "ghp_abcdefghijklmnopqrstuvwxyz0123456789", "alias helper=\"setopt rcquotes; printf '%s' 'ghp_abcdefghijklmno''pqrstuvwxyz0123456789'\"\n", ), ( "ghp_abcdefghijklmnopqrstuvwxyz0123456789", "alias helper=\"printf '%s' 'ghp_abcdefghijkl'\\\\\n'mnopqrstuvwxyz0123456789'\"\nsetopt rcquotes\n", ), ] { let startup_path = dir.path().join("startup.sh"); std::fs::write(&startup_path, startup)?; let output = Command::new("/bin/zsh") .args(["-fc", &format!(". \"$1\"\n{script}"), "snapshot"]) .arg(startup_path) .env_clear() .env("HOME", dir.path()) .output()?; assert!(output.status.success(), "{output:?}"); let snapshot = captured_script(ShellType::Zsh, &String::from_utf8(output.stdout)?)?; let path = dir.path().join("snapshot.sh"); std::fs::write(&path, &snapshot)?; let replay = Command::new("/bin/zsh") .args(["-fc", ". \"$1\"; eval helper", "snapshot"]) .arg(path) .env_clear() .output()?; assert!(replay.status.success(), "{replay:?}"); assert_eq!(String::from_utf8(replay.stdout)?, real); assert!( super::literals::literal_words(&snapshot, ShellType::Zsh) .expect("valid snapshot") .contains(&real.to_string()), "runtime literal was not checked: {snapshot}" ); } Ok(()) } #[test] fn zsh_snapshot_checks_rcquotes_without_changing_alias_behavior() -> Result<()> { if !std::path::Path::new("/bin/zsh").exists() { return Ok(()); } let dir = tempdir()?; let real = "token_ab'cdefghijklmnopqrstuvwx"; let original = HashMap::from([("VENDOR_PASSWORD".to_string(), real.to_string())]); let allowed = HashMap::from([( "VENDOR_PASSWORD".to_string(), "token_dummy_abcdefghijklmnopqr".to_string(), )]); let brokered_keys = vec!["VENDOR_PASSWORD".to_string()]; let script = snapshot_capture_script(ShellType::Zsh, CAPTURE_NON_INTERACTIVE) .expect("zsh supports snapshots"); for (option, option_value) in [("setopt rcquotes", "on"), ("unsetopt rcquotes", "off")] { for (value, accepted) in [(real, false), ("ordinary'quoted", true)] { for alias in [ r#"alias helper="printf '%s' \"$ALIAS_VALUE\"""#, r#"alias helper="printf '%s' ${(qq)ALIAS_VALUE}""#, r#"alias helper="printf '%s' ${(qq)ALIAS_VALUE}\"\${EMPTY-}\"""#, ] { let output = Command::new("/bin/zsh") .args([ "-fc", &format!("{option}\n{alias}\nunset ALIAS_VALUE\n{script}"), ]) .env_clear() .envs(&original) .env("HOME", dir.path()) .env("ALIAS_VALUE", value) .output()?; assert!(output.status.success(), "{output:?}"); let mut snapshot = String::from_utf8(output.stdout)?; let rewritten = rewrite_snapshot_credentials( ShellType::Zsh, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &original, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|_| false, brokered_keys: &brokered_keys, brokered_alias_keys: &[], allowed_brokered_keys: &brokered_keys, }, |text| { *text = text.replace(real, &allowed["VENDOR_PASSWORD"]); true }, ); assert_eq!( rewritten.is_some(), accepted, "{option}, {alias}: {snapshot}" ); if accepted { let path = dir.path().join("snapshot.sh"); std::fs::write(&path, &snapshot)?; let replay = Command::new("/bin/zsh") .args([ "-fc", ". \"$1\"; eval helper; printf '\\n%s\\n' $options[rcquotes]", "snapshot", ]) .arg(path) .env_clear() .envs(&allowed) .output()?; assert!(replay.status.success(), "{replay:?}"); assert_eq!( String::from_utf8(replay.stdout)?, format!("{value}\n{option_value}\n") ); } } } } Ok(()) } #[test] fn brokered_snapshots_check_static_fragments_next_to_expansions() -> Result<()> { let real = "password$abcdefghijklmno"; let dir = tempdir()?; let invocation = dir.path().join("invoke.sh"); std::fs::write(&invocation, "helper\n")?; let inner_invocation = dir.path().join("inner-invoke.sh"); std::fs::write(&inner_invocation, "inner\n")?; for (shell, aliases) in [("/bin/bash", "alias -p"), ("/bin/zsh", "alias -L")] { if !std::path::Path::new(shell).exists() { continue; } for (body, accepted) in [ (r#"printf '%s' "${USER}:password\$abcdefghijklmno""#, false), (r#"printf '%s' "${USER}:pass"'word$abcdefghijklmno'"#, false), (r#"printf '%s' "password\$abcdefghijklmno:${USER}""#, false), ( r#"printf '%s' "${USER}:"$'password\x24abcdefghijklmno'"#, false, ), (r#"printf '%s' "${USER}:$VENDOR_PASSWORD""#, true), (r#"printf '%s' "${USER}:ordinary\$value""#, true), ( "cat < Result<()> { let dir = tempdir()?; let startup = dir.path().join("startup.sh"); let real = "ghp_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH"; let dummy = "ghp_brokered_dummy_abcdefghijklmnopqrstuvwxyz0123"; let credential_shaped_function = "ghp_0123456789abcdefghijklmnopqrstuvwxyzABCD"; std::fs::write( &startup, format!( "helper() {{\n cat <<'EOF'\nexport LEGACY_SETTING=production\nAuthorization: {real}\nEOF\n}}\n{credential_shaped_function}() {{ printf UNEXPECTED_EXECUTION; }}\nexport -f helper\nexport GH_TOKEN='{real}'\n" ), )?; let output = Command::new("/bin/bash") .arg("-c") .arg( snapshot_capture_script(ShellType::Bash, CAPTURE_ALL).expect("bash supports snapshots"), ) .env_clear() .env("HOME", dir.path()) .env("PATH", "/usr/bin:/bin") .env("BASH_ENV", &startup) .output()?; assert!(output.status.success()); let mut snapshot = String::from_utf8(output.stdout)?; let original = HashMap::from([("GH_TOKEN".to_string(), real.to_string())]); let discovered = HashMap::from([("GH_TOKEN".to_string(), dummy.to_string())]); let brokered_keys = vec!["GH_TOKEN".to_string()]; let snapshot_is_safe = rewrite_snapshot_credentials( ShellType::Bash, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &original, configured: &HashMap::new(), discovered: &discovered, allowed: &discovered, is_allowed_unset: &|_| false, brokered_keys: &brokered_keys, brokered_alias_keys: &[], allowed_brokered_keys: &brokered_keys, }, |text| { if text.contains(credential_shaped_function) { *text = text.replace(credential_shaped_function, ""); return false; } *text = text.replace(real, dummy); true }, ); assert!(snapshot_is_safe.is_none()); assert!(snapshot.contains(real)); assert!(snapshot.contains(credential_shaped_function)); Ok(()) } #[test] fn brokered_zsh_tied_alias_respects_policy_override() -> Result<()> { if !std::path::Path::new("/bin/zsh").exists() { return Ok(()); } let real = "ghp_abcdefghijklmnopqrstuvwxyz0123456789"; let dummy = "ghp_dummy_abcdefghijklmnopqrstuvwxyz0123"; let keys = vec!["GH_TOKEN".to_string()]; let script = snapshot_capture_script(ShellType::Zsh, CAPTURE_NON_INTERACTIVE).unwrap(); for (replacement, source_present, source_allowed) in ["", "replacement|plain|replacement"] .into_iter() .flat_map(|replacement| { [ (replacement, true, true), (replacement, true, false), (replacement, false, false), ] }) { let unset_source = if source_present { "" } else { "unset GH_TOKEN" }; let output = Command::new("/bin/zsh") .args([ "-fc", &format!( "export -UT AUTH_HEADER auth_header=(\"Bearer $GH_TOKEN\" plain) '|'\n{unset_source}\n{script}" ), ]) .env_clear() .env("GH_TOKEN", real) .output()?; assert!(output.status.success(), "{output:?}"); let mut snapshot = String::from_utf8(output.stdout)?; let mut original = HashMap::from([ ("GH_TOKEN".to_string(), real.to_string()), ("AUTH_HEADER".to_string(), format!("Bearer {real}|plain")), ]); let brokered_keys = if source_present { keys.as_slice() } else { original.remove("GH_TOKEN"); &[] }; let discovered = original .iter() .map(|(key, value)| (key.clone(), value.replace(real, dummy))) .collect::>(); let configured = HashMap::from([("AUTH_HEADER".to_string(), replacement.to_string())]); let mut allowed = discovered.clone(); allowed.extend(configured.clone()); let allowed_keys = if source_allowed { keys.as_slice() } else { allowed.remove("GH_TOKEN"); &[] }; let prepared = rewrite_snapshot_credentials( ShellType::Zsh, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &original, configured: &configured, discovered: &discovered, allowed: &allowed, is_allowed_unset: &|_| true, brokered_keys, brokered_alias_keys: &[], allowed_brokered_keys: allowed_keys, }, |value| { if !source_allowed && (value.contains(real) || value.contains(dummy)) { return false; } *value = value.replace(real, dummy); true }, ) .expect("policy override keeps the tied alias usable"); assert_eq!( prepared.aliases, if source_present { configured } else { HashMap::new() } ); assert!(!snapshot.contains(real)); let inspect = "printf '%s\\0' \"$AUTH_HEADER\" \"${auth_header[@]}\"; AUTH_HEADER='next|next|last'; printf '%s\\0' \"$AUTH_HEADER\" \"${auth_header[@]}\""; let mut outputs = Vec::new(); for setup in [snapshot.as_str(), "export -UT AUTH_HEADER auth_header '|'"] { let output = Command::new("/bin/zsh") .args(["-fc", &format!("set -e; {setup}\n{inspect}")]) .env_clear() .env("AUTH_HEADER", replacement) .output()?; assert!(output.status.success(), "{output:?}"); outputs.push(output.stdout); } assert_eq!(outputs[0], outputs[1]); } Ok(()) } #[test] fn brokered_snapshot_alias_values_follow_allowed_credential_overrides() { let real = "ghp_abcdefghijklmnopqrstuvwxyz0123456789"; let discovered_dummy = "ghp_discovered_dummy_abcdefghijklmnopqr"; let configured_dummy = "ghp_configured_dummy_abcdefghijklmnopqr"; let original = HashMap::from([ ("GH_TOKEN".to_string(), real.to_string()), ("AUTH_HEADER".to_string(), format!("Bearer {real}")), ]); let discovered = HashMap::from([("GH_TOKEN".to_string(), discovered_dummy.to_string())]); let brokered_keys = vec!["GH_TOKEN".to_string()]; for (source_allowed, alias, expected) in [ ( true, format!("Bearer {discovered_dummy}"), Some(( format!("Bearer {configured_dummy}"), r#"export AUTH_HEADER='Bearer '"${GH_TOKEN-}""#, )), ), ( true, String::new(), Some((String::new(), "export AUTH_HEADER=''")), ), (false, format!("Bearer {discovered_dummy}"), None), ] { let mut allowed = HashMap::from([ ("GH_TOKEN".to_string(), configured_dummy.to_string()), ("AUTH_HEADER".to_string(), alias), ]); let allowed_brokered_keys = if source_allowed { brokered_keys.as_slice() } else { allowed.remove("GH_TOKEN"); &[] }; let mut snapshot = format!("# Snapshot file\n\0\0AUTH_HEADER\0export AUTH_HEADER='Bearer {real}'\n\0\0"); let prepared = rewrite_snapshot_credentials( ShellType::Bash, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &original, configured: &HashMap::new(), discovered: &discovered, allowed: &allowed, is_allowed_unset: &|_| false, brokered_keys: &brokered_keys, brokered_alias_keys: &[], allowed_brokered_keys, }, |text| { *text = text.replace(real, discovered_dummy); true }, ); let expected = match expected { Some((value, assignment)) => ( format!("# Snapshot file\n# exports (native declarations)\n{assignment}\n"), HashMap::from([("AUTH_HEADER".to_string(), value)]), Vec::new(), ), None => ( "# Snapshot file\n".to_string(), HashMap::new(), vec!["AUTH_HEADER".to_string()], ), }; assert_eq!( prepared.map(|rewrite| (rewrite.script, rewrite.aliases, rewrite.rejected_alias_keys)), Some(expected), ); } } #[test] fn brokered_short_alias_respects_policy_override() -> Result<()> { let real = "pin_abcdefgh"; let dummy = "pin_qrstuvwx"; let keys = vec!["VENDOR_PASSWORD".to_string()]; for captured in [real, "pin_ijklmnop"] { let original = HashMap::from([ ("VENDOR_PASSWORD".to_string(), captured.to_string()), ("AUTH_HEADER".to_string(), format!("Bearer {captured}")), ]); let restored = HashMap::from([ ("VENDOR_PASSWORD".to_string(), real.to_string()), ("AUTH_HEADER".to_string(), format!("Bearer {real}")), ]); let discovered = HashMap::from([ ("VENDOR_PASSWORD".to_string(), dummy.to_string()), ("AUTH_HEADER".to_string(), format!("Bearer {dummy}")), ]); for replacement in ["", "replacement"] { let configured = HashMap::from([("AUTH_HEADER".to_string(), replacement.to_string())]); let mut allowed = discovered.clone(); allowed.extend(configured.clone()); let mut snapshot = format!( "# Snapshot file\n\0\0AUTH_HEADER\0export AUTH_HEADER='Bearer {captured}'\n\0\0" ); let prepared = rewrite_snapshot_credentials( ShellType::Bash, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &restored, configured: &configured, discovered: &discovered, allowed: &allowed, is_allowed_unset: &|_| false, brokered_keys: &keys, brokered_alias_keys: &[], allowed_brokered_keys: &keys, }, |value| { *value = value.replace(real, dummy).replace(captured, dummy); true }, ) .expect("policy override keeps short aliases usable"); assert_eq!(prepared.aliases, configured); assert!(!snapshot.contains(real)); assert!(!snapshot.contains(captured)); let output = Command::new("/bin/bash") .args(["-c", &format!("{snapshot}\nprintf '%s' \"$AUTH_HEADER\"")]) .env_clear() .output()?; assert!(output.status.success(), "{output:?}"); assert_eq!(output.stdout, replacement.as_bytes()); } } Ok(()) } #[test] fn brokered_snapshot_aliases_follow_captured_dummies() -> Result<()> { for (real, captured, dummy) in [ ( "ghp_real_abcdefghijklmnopqrstuvwxyz012345", "ghp_old_abcdefghijklmnopqrstuvwxyz0123456", "ghp_new_abcdefghijklmnopqrstuvwxyz0123456", ), ("pin_abcdefgh", "pin_ijklmnop", "pin_ijklmnop"), ("pin_abcdefgh", "pin_ijklmnop", "pin_qrstuvwx"), ] { let original = HashMap::from([ ("VENDOR_PASSWORD".to_string(), captured.to_string()), ("AUTH_HEADER".to_string(), format!("Bearer {captured}")), ("READONLY_HEADER".to_string(), format!("Bearer {captured}")), ]); let mut restored = original.clone(); restored.insert("VENDOR_PASSWORD".to_string(), real.to_string()); let allowed = HashMap::from([ ("VENDOR_PASSWORD".to_string(), dummy.to_string()), ("AUTH_HEADER".to_string(), format!("Bearer {dummy}")), ("READONLY_HEADER".to_string(), format!("Bearer {dummy}")), ]); let keys = vec!["VENDOR_PASSWORD".to_string()]; let mut snapshot = format!( "# Snapshot file\n\0\0AUTH_HEADER\0export AUTH_HEADER='Bearer {captured}'\n\0READONLY_HEADER\0declare -rx READONLY_HEADER='Bearer {captured}'\n\0\0" ); let rewrite = rewrite_snapshot_credentials( ShellType::Bash, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &restored, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|_| false, brokered_keys: &keys, brokered_alias_keys: &[], allowed_brokered_keys: &keys, }, |text| { *text = text .replace(&format!("Bearer {real}"), &format!("Bearer {dummy}")) .replace(&format!("Bearer {captured}"), &format!("Bearer {dummy}")); true }, ) .expect("captured dummy aliases should be rewritten"); assert_eq!( rewrite.aliases, HashMap::from([ ("AUTH_HEADER".to_string(), format!("Bearer {dummy}")), ("READONLY_HEADER".to_string(), format!("Bearer {dummy}")), ]) ); let replay = Command::new("/bin/bash") .args([ "-c", &format!("{snapshot}\nprintf '%s\\n%s' \"$AUTH_HEADER\" \"$READONLY_HEADER\""), ]) .env_clear() .env("VENDOR_PASSWORD", real) .output()?; assert!(replay.status.success(), "{replay:?}"); assert_eq!( replay.stdout, format!("Bearer {real}\nBearer {real}").as_bytes() ); if std::path::Path::new("/bin/zsh").exists() { let script = snapshot_capture_script(ShellType::Zsh, CAPTURE_NON_INTERACTIVE).unwrap(); let capture_output = Command::new("/bin/zsh") .args(["-fc", &format!("readonly READONLY_HEADER\n{script}")]) .env_clear() .envs(&original) .env("PATH", "/usr/bin:/bin") .output()?; assert!(capture_output.status.success(), "{capture_output:?}"); let mut snapshot = String::from_utf8(capture_output.stdout)?; let rewritten = rewrite_snapshot_credentials( ShellType::Zsh, &mut snapshot, SnapshotCredentialEnvironment { original: &original, restored: &restored, configured: &HashMap::new(), discovered: &allowed, allowed: &allowed, is_allowed_unset: &|_| false, brokered_keys: &keys, brokered_alias_keys: &[], allowed_brokered_keys: &keys, }, |text| { *text = text.replace(real, dummy).replace(captured, dummy); true }, ) .expect("omitted readonly exports retain their alias metadata"); assert_eq!(rewritten.aliases, rewrite.aliases); let replay = Command::new("/bin/zsh") .args(["-fc", &format!("readonly READONLY_HEADER\n{snapshot}\n[[ $parameters[READONLY_HEADER] == *readonly* ]] || exit 1\nprintf '%s' \"$READONLY_HEADER\"")]) .env_clear() .env("VENDOR_PASSWORD", real) .env("READONLY_HEADER", format!("Bearer {real}")) .output()?; assert!(replay.status.success(), "{replay:?}"); assert_eq!(replay.stdout, format!("Bearer {real}").as_bytes()); } } Ok(()) } #[test] fn brokered_snapshot_rejects_shell_escaped_credentials() { let real = "token_ab'cdefgh"; let dummy = "token_xy'zabcde"; let mut snapshot = format!( "# Snapshot file\n# aliases 1\nalias helper='tool {escaped}'\n# exports 0\n", escaped = real.replace('\'', r#"'\''"#), ); let original = snapshot.clone(); let snapshot_is_safe = rewrite_snapshot_credentials( ShellType::Bash, &mut snapshot, empty_snapshot_credentials(&HashMap::new(), &|_| false), |text| { *text = text.replace(real, dummy); true }, ); assert!(snapshot_is_safe.is_none()); assert_eq!(snapshot, original); } #[cfg(target_os = "macos")] #[test] fn zsh_snapshot_restores_tied_path() -> Result<()> { let dir = tempdir()?; let path_with_spaces = dir.path().join("path with spaces").join("bin"); let plain_path = dir.path().join("plain-path").join("bin"); let expected_path = format!( "{}:{}:/usr/bin:/bin", path_with_spaces.display(), plain_path.display() ); let zshrc = format!( "export -UT PATH path=('{}' '{}' '{}' /usr/bin /bin)\n", path_with_spaces.display(), plain_path.display(), plain_path.display() ); std::fs::write(dir.path().join(".zshrc"), zshrc)?; let snapshot = Command::new("/bin/zsh") .arg("-f") .arg("-c") .arg(snapshot_script(ShellType::Zsh).expect("zsh supports snapshots")) .env_clear() .env("PATH", "/usr/bin:/bin") .env("ZDOTDIR", dir.path()) .output()?; assert!(snapshot.status.success()); let snapshot_path = dir.path().join("snapshot.sh"); let snapshot = captured_script(ShellType::Zsh, &String::from_utf8(snapshot.stdout)?)?; std::fs::write(&snapshot_path, &snapshot)?; let restored = Command::new("/bin/zsh") .arg("-f") .arg("-c") .arg("set -e; . \"$1\"; print -r -- \"$PATH\"") .arg("zsh") .arg(&snapshot_path) .env_clear() .env("PATH", "/usr/bin:/bin") .output()?; assert!(restored.status.success()); assert_eq!( String::from_utf8(restored.stdout)?.trim_end(), expected_path ); assert!( snapshot .lines() .any(|line| line.starts_with("export -UT PATH path=")), "snapshot should capture the tied PATH export" ); std::fs::write(dir.path().join(".zshrc"), "readonly PATH\n")?; let readonly_snapshot = Command::new("/bin/zsh") .arg("-f") .arg("-c") .arg(snapshot_script(ShellType::Zsh).expect("zsh supports snapshots")) .env_clear() .env("PATH", "/usr/bin:/bin") .env("ZDOTDIR", dir.path()) .output()?; assert!(readonly_snapshot.status.success()); let readonly_snapshot = captured_script( ShellType::Zsh, &String::from_utf8(readonly_snapshot.stdout)?, )?; std::fs::write(&snapshot_path, &readonly_snapshot)?; let readonly_restored = Command::new("/bin/zsh") .arg("-f") .arg("-c") .arg("set -e; . \"$1\"; export PATH='/codex-path':\"$PATH\"; print -r -- \"$PATH\"") .arg("zsh") .arg(&snapshot_path) .env_clear() .env("PATH", "/usr/bin:/bin") .output()?; assert!(readonly_restored.status.success()); assert_eq!( String::from_utf8(readonly_restored.stdout)?.trim_end(), "/codex-path:/usr/bin:/bin" ); assert!( !readonly_snapshot .lines() .any(|line| line.starts_with("export -rT PATH path=")), "snapshot should not capture the readonly tied PATH export" ); Ok(()) } // Literal-only fixtures are shell source rather than capture output. Give those fixtures // empty alias/export sections; real shell fixtures exercise the full framed capture. fn rewrite_snapshot_credentials( shell_type: ShellType, snapshot: &mut String, environment: SnapshotCredentialEnvironment<'_>, virtualize_text: impl FnMut(&mut String) -> bool, ) -> Option { let source_only = !snapshot.contains('\0'); let framed = if source_only { format!("# Snapshot file\n{snapshot}\0\0\0") } else { snapshot.clone() }; let capture = CapturedSnapshot::parse(shell_type, framed.as_bytes())?; let prepared = prepare_snapshot_credentials(&capture, environment, virtualize_text)?; *snapshot = if source_only { prepared .script .strip_prefix("# Snapshot file\n")? .to_string() } else { prepared.script.clone() }; Some(prepared) } fn captured_script(shell_type: ShellType, source: &str) -> Result { let captured = CapturedSnapshot::parse(shell_type, source.as_bytes()).context("invalid native capture")?; Ok(captured.render_script()) }