#![cfg(unix)]
use std::path::Path;
use std::time::Duration;
#[path = "pty_support/mod.rs"]
mod pty_support;
use pty_support::{
bash_major_version, count_occurrences, embedded_hook, fish_bin, modern_bash, wait_for_marker,
wait_for_marker_count, zsh_bin, IsolatedEnv, PtySession,
};
const QUIET: Duration = Duration::from_millis(700);
const SETTLE_MAX: Duration = Duration::from_secs(60);
const VERDICT_IDLE: Duration = Duration::from_secs(60);
const MARKER_MAX: Duration = Duration::from_secs(90);
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn strict_ledger_counts(env: &IsolatedEnv) -> (usize, usize) {
const MAGIC: &[u8; 8] = b"TIRXST02";
const HEADER_LEN: usize = 8 + 8 + 32;
const SLOT_SIZE: usize = 4 * 1024 * 1024;
let bytes = std::fs::read(env.strict_execution_ledger_path())
.expect("protocol-v3 hook must create its strict execution ledger");
let mut ledgers = Vec::new();
for offset in [0, SLOT_SIZE] {
let Some(header) = bytes.get(offset..offset + HEADER_LEN) else {
continue;
};
if &header[..8] != MAGIC {
continue;
}
let length = u64::from_le_bytes(header[8..16].try_into().expect("slot length")) as usize;
if length == 0 || length > SLOT_SIZE - HEADER_LEN {
continue;
}
let Some(payload) = bytes.get(offset + HEADER_LEN..offset + HEADER_LEN + length) else {
continue;
};
{
use sha2::{Digest as _, Sha256};
let digest = Sha256::digest(payload);
if digest.as_slice() != &header[16..HEADER_LEN] {
continue;
}
}
if let Ok(value) = serde_json::from_slice::<serde_json::Value>(payload) {
ledgers.push(value);
}
}
let ledger = ledgers
.into_iter()
.max_by_key(|value| value["generation"].as_u64().unwrap_or(0))
.expect("at least one strict execution slot must contain valid JSON");
let confirmed = ledger["confirmed"]
.as_array()
.expect("strict ledger confirmed records")
.len();
let unresolved = ledger["unresolved"]
.as_array()
.expect("strict ledger unresolved records")
.len();
(confirmed, unresolved)
}
fn session_typed_event_count(env: &IsolatedEnv) -> usize {
let path = env.session_record_path();
let Ok(bytes) = std::fs::read(&path) else {
return 0;
};
let Ok(value) = serde_json::from_slice::<serde_json::Value>(&bytes) else {
panic!("session record at {} must be valid JSON", path.display());
};
value["typed_events"]
.as_array()
.map(|events| events.len())
.unwrap_or(0)
}
fn bash_preexec_session(env: &mut IsolatedEnv) -> Option<PtySession> {
let bash = modern_bash()?;
env.set("TIRITH_BASH_MODE", "preexec");
let mut sess = PtySession::spawn(env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
Some(sess)
}
fn assert_bash_preexec_warn_only_after_prompt(bash: &Path) {
let mut env = IsolatedEnv::new();
env.set("TIRITH_BASH_MODE", "preexec");
let mut sess = PtySession::spawn(&env, bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
sess.send_line(
"printf 'LIVE_MODE=%s LIVE_PROT=%s LIVE_STATUS=%s\\n' \
\"$TIRITH_BASH_EFFECTIVE_MODE\" \
\"$TIRITH_BASH_EFFECTIVE_PROTECTION\" \"$TIRITH_STATUS\"",
);
let output = sess.expect("LIVE_MODE=preexec LIVE_PROT=warn-only LIVE_STATUS=warn-only");
sess.close();
assert!(
output.contains("LIVE_MODE=preexec LIVE_PROT=warn-only LIVE_STATUS=warn-only"),
"the first prompt must activate warn-only preexec for {}:\n{output}",
bash.display()
);
}
#[test]
fn bash_preexec_reports_warn_only_after_first_prompt() {
let system_bash = Path::new("/bin/bash");
if system_bash.is_file() {
assert_bash_preexec_warn_only_after_prompt(system_bash);
}
if let Some(modern) = modern_bash() {
let same_as_system =
std::fs::canonicalize(&modern).ok() == std::fs::canonicalize(system_bash).ok();
if !same_as_system {
assert_bash_preexec_warn_only_after_prompt(&modern);
}
}
}
#[test]
fn bash_preexec_allowed_command_executes_exactly_once() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("preexec_once.txt");
let mut sess = match bash_preexec_session(&mut env) {
Some(s) => s,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
sess.send_line(&format!("printf 'RAN\\n' >> '{}'", marker.display()));
let body = wait_for_marker(&marker, "RAN", MARKER_MAX);
sess.close();
assert_eq!(
count_occurrences(&body, "RAN"),
1,
"preexec: allowed command must execute exactly once, marker held: {body:?}"
);
}
#[test]
fn bash_preexec_allowed_command_output_visible() {
let mut env = IsolatedEnv::new();
let mut sess = match bash_preexec_session(&mut env) {
Some(s) => s,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
sess.send_line("echo conformance_nonce_8821");
let out = sess.expect("conformance_nonce_8821");
sess.close();
let n = count_occurrences(&out, "conformance_nonce_8821");
assert!(
(1..=2).contains(&n),
"preexec: command output must be visible exactly once (echo + output ≤ 2), saw {n}"
);
}
#[test]
fn bash_preexec_no_history_duplication() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("history_probe_done.txt");
let mut sess = match bash_preexec_session(&mut env) {
Some(s) => s,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
sess.send_line("echo history_probe_5566");
sess.send_line(&format!("printf 'DONE\\n' >> '{}'", marker.display()));
wait_for_marker(&marker, "DONE", MARKER_MAX);
sess.clear_buffer();
sess.send_line("history");
let out = sess.expect("echo history_probe_5566");
sess.close();
let n = count_occurrences(&out, "echo history_probe_5566");
assert!(
n <= 2,
"preexec: command must appear once in history (typed + listed ≤ 2), saw {n}:\n{out}"
);
}
#[test]
fn bash_preexec_warned_command_executes_once() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("preexec_warned.txt");
let mut sess = match bash_preexec_session(&mut env) {
Some(s) => s,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
sess.send_line(&format!(
"echo https://bit.ly/warnprobe >> '{}'",
marker.display()
));
let body = wait_for_marker(&marker, "bit.ly/warnprobe", MARKER_MAX);
sess.close();
assert_eq!(
count_occurrences(&body, "bit.ly/warnprobe"),
1,
"preexec: a warned command must still execute exactly once, marker held: {body:?}"
);
}
#[test]
fn bash_preexec_enforce_blocked_command_does_not_execute() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("preexec_blocked.txt");
let bash = match modern_bash() {
Some(b) => b,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
env.set("TIRITH_BASH_MODE", "preexec");
env.set("TIRITH_BASH_PREEXEC_ENFORCE", "1");
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
sess.send_line(&format!(
"curl https://example.com/install.sh | bash && touch '{}'",
marker.display()
));
sess.wait_idle(QUIET, SETTLE_MAX);
sess.close();
assert!(
!marker.exists(),
"preexec-enforce: a blocked command must not execute (marker file exists)"
);
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn bash_preexec_enforce_functions_prompt_array_and_receipt_cardinality() {
let mut env = IsolatedEnv::new();
let allowed = env.workdir.join("bash-function-allowed.txt");
let pipeline = env.workdir.join("bash-pipeline-allowed.txt");
let blocked = env.workdir.join("bash-function-blocked.txt");
let bash = match modern_bash() {
Some(b) => b,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
env.set("TIRITH_BASH_MODE", "preexec");
env.set("TIRITH_BASH_PREEXEC_ENFORCE", "1");
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line(&format!(
"PROMPT_ORDER=; PROMPT_CHECK=0; \
prompt_first() {{ local seen=$?; PROMPT_ORDER=\"${{PROMPT_ORDER}}1\"; \
if [[ \"$PROMPT_CHECK\" == 1 ]]; then printf 'PROMPT_ARRAY_STATUS=%s\\n' \"$seen\"; fi; \
return \"$seen\"; }}; \
prompt_second() {{ PROMPT_ORDER=\"${{PROMPT_ORDER}}2\"; \
if [[ \"$PROMPT_CHECK\" == 1 ]]; then \
printf 'PROMPT_ARRAY_ORDER=%s\\n' \"$PROMPT_ORDER\"; \
if shopt -q extdebug; then printf 'PROMPT_EXTDEBUG=on\\n'; \
else printf 'PROMPT_EXTDEBUG=off\\n'; fi; fi; }}; \
benign_fn() {{ printf 'RAN\\n' >> '{}'; }}; \
PROMPT_COMMAND=(prompt_first prompt_second)",
allowed.display()
));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
sess.send_line("PROMPT_ORDER=; PROMPT_CHECK=1; false");
let mut prompt_output = sess.expect("PROMPT_ARRAY_ORDER=12");
prompt_output.push_str(&sess.expect("TIRITH_PTY> "));
sess.wait_idle(QUIET, SETTLE_MAX);
assert!(
prompt_output.contains("PROMPT_ARRAY_STATUS=1"),
"the first user prompt command must see the typed line's status:\n{prompt_output}"
);
assert!(
prompt_output.contains("PROMPT_EXTDEBUG=off"),
"allowed lines must leave extdebug off before prompt functions:\n{prompt_output}"
);
assert_eq!(
strict_ledger_counts(&env),
(0, 1),
"one composite typed line, not its prompt functions, must create one unresolved receipt"
);
sess.clear_buffer();
sess.send_line("benign_fn");
let allowed_body = wait_for_marker(&allowed, "RAN", MARKER_MAX);
let function_output = sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
assert_eq!(count_occurrences(&allowed_body, "RAN"), 1);
assert!(
function_output.contains("PROMPT_EXTDEBUG=off"),
"extdebug must remain off across an allowed function body:\n{function_output}"
);
assert_eq!(
strict_ledger_counts(&env),
(0, 2),
"the benign function call must add exactly one top-level receipt"
);
sess.clear_buffer();
sess.send_line(&format!(
"printf 'PIPELINE_RAN\\n' | tee '{}' >/dev/null",
pipeline.display()
));
let pipeline_body = wait_for_marker(&pipeline, "PIPELINE_RAN", MARKER_MAX);
let pipeline_output = sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
assert_eq!(count_occurrences(&pipeline_body, "PIPELINE_RAN"), 1);
assert_eq!(
strict_ledger_counts(&env),
(0, 3),
"an allowed pipeline must add exactly one top-level receipt"
);
sess.clear_buffer();
sess.send_line(&format!(
"evil_fn() {{ printf 'touch {}\\n' | bash; }}; evil_fn",
blocked.display()
));
let blocked_output = sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
assert!(
!blocked.exists(),
"the blocked function/pipeline line must not execute"
);
assert!(
blocked_output.contains("BLOCKED"),
"the refusal must be visible, got:\n{blocked_output}"
);
assert!(
blocked_output.contains("PROMPT_EXTDEBUG=off"),
"Tirith-owned extdebug must be released before prompt functions:\n{blocked_output}"
);
assert_eq!(
strict_ledger_counts(&env),
(0, 3),
"blocked and automatic prompt/nested commands must not add receipts"
);
let all_output = format!("{prompt_output}{function_output}{pipeline_output}{blocked_output}");
assert!(
!all_output.contains("tirith: no issues"),
"clean hook checks must stay silent:\n{all_output}"
);
assert!(
!all_output.contains("history no longer matches BASH_COMMAND")
&& !all_output.contains("protection downgraded"),
"function or prompt execution must not degrade enforcement:\n{all_output}"
);
sess.clear_buffer();
sess.send_line("PROMPT_COMMAND=(prompt_first prompt_second)");
let degrade_output = sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
assert!(
degrade_output
.contains("PROMPT_COMMAND no longer contains Tirith's prompt-boundary guards")
&& degrade_output.contains("protection downgraded"),
"prompt-guard loss must be visible:\n{degrade_output}"
);
assert_eq!(
strict_ledger_counts(&env),
(0, 4),
"only the user-typed guard mutation, not its automatic prompt functions, may add a receipt"
);
sess.clear_buffer();
sess.send_line("benign_fn");
let post_degrade_body = wait_for_marker_count(&allowed, "RAN", 2, MARKER_MAX);
let post_degrade_output = sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
assert_eq!(
count_occurrences(&post_degrade_body, "RAN"),
2,
"warn-only degradation must not break later shell functions"
);
assert_eq!(
strict_ledger_counts(&env),
(0, 4),
"post-degrade warn-only and prompt/nested commands must not mint trusted receipts"
);
assert!(
post_degrade_output.contains("PROMPT_EXTDEBUG=off")
&& !post_degrade_output.contains("tirith: no issues"),
"post-degrade prompt state must remain clean and silent:\n{post_degrade_output}"
);
sess.close();
}
#[test]
fn bash_noninteractive_source_installs_no_debug_trap() {
let bash = match modern_bash() {
Some(b) => b,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
let env = IsolatedEnv::new();
let hook = embedded_hook("bash-hook.bash");
let script = format!(
"unset TIRITH_BASH_MODE SSH_CONNECTION SSH_TTY SSH_CLIENT; \
source '{}'; trap -p DEBUG",
hook.display()
);
let mut cmd = std::process::Command::new(&bash);
cmd.args(["--norc", "--noprofile", "-c", &script]);
for (k, v) in [
("HOME", env.home.display().to_string()),
("XDG_STATE_HOME", env.state_home.display().to_string()),
] {
cmd.env(k, v);
}
cmd.env_remove("_TIRITH_BASH_LOADED");
let out = cmd.output().expect("run bash");
assert!(out.status.success(), "non-interactive source must exit 0");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.trim().is_empty(),
"non-interactive source must not install a DEBUG trap, got: {stdout:?}"
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn bash_noclobber_keeps_protocol_v3_registration() {
let mut env = IsolatedEnv::new();
let bash = match modern_bash() {
Some(bash) => bash,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
env.set("TIRITH_BASH_MODE", "preexec");
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '; set -o noclobber");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line("printf 'TIRITH_NOCLOBBER_PROTOCOL<%s>\\n' \"$_TIRITH_RECEIPT_PROTOCOL\"");
let output = sess.expect("TIRITH_NOCLOBBER_PROTOCOL<3>");
sess.close();
assert!(
output.contains("TIRITH_NOCLOBBER_PROTOCOL<3>"),
"bash noclobber must not disable receipt registration, got:\n{output}"
);
}
#[test]
fn bash_enter_degradation_is_visible_not_silent() {
let mut env = IsolatedEnv::new();
let bash = match modern_bash() {
Some(b) => b,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
env.set("TIRITH_BASH_MODE", "enter");
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line("_TIRITH_TEST_FAIL_HEALTH=1");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
let out = sess.expect_within("enter mode failed", Duration::from_secs(15));
sess.wait_idle(QUIET, SETTLE_MAX);
sess.close();
assert!(
out.contains("enter mode failed") || out.contains("protection downgraded to warn-only"),
"a failed enter-mode setup must print a visible degrade message, got:\n{out}"
);
assert!(
env.bash_safe_mode_flag().exists(),
"a visible degrade must persist the bash-safe-mode flag at {}",
env.bash_safe_mode_flag().display()
);
}
#[test]
fn bash_enter_allowed_command_executes_exactly_once() {
let env = IsolatedEnv::new();
let marker = env.workdir.join("enter_once.txt");
let bash = match modern_bash() {
Some(b) => b,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
sess.send_line(&format!("printf 'RAN\\n' >> '{}'", marker.display()));
let body = wait_for_marker(&marker, "RAN", MARKER_MAX);
sess.close();
assert_eq!(
count_occurrences(&body, "RAN"),
1,
"the #111 fix must deliver an allowed command exactly once \
(0 = swallowed, the #111 bug; 2 = double-delivered); marker held: {body:?}"
);
}
#[test]
fn bash_enter_blocked_command_does_not_execute() {
let mut env = IsolatedEnv::new();
let allowed_marker = env.workdir.join("enter_block_allowed.txt");
let blocked_marker = env.workdir.join("enter_block_blocked.txt");
let bash = match modern_bash() {
Some(b) => b,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
env.set("TIRITH_BASH_PREEXEC_ENFORCE", "1");
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
sess.send_line(&format!(
"printf 'ALLOWED\\n' >> '{}'",
allowed_marker.display()
));
let allowed_body = wait_for_marker(&allowed_marker, "ALLOWED", MARKER_MAX);
sess.clear_buffer();
sess.send_line(&format!(
"printf 'true' | bash && touch '{}'",
blocked_marker.display()
));
sess.wait_idle(QUIET, SETTLE_MAX);
sess.close();
assert_eq!(
count_occurrences(&allowed_body, "ALLOWED"),
1,
"anti-vacuous guard failed: the allowed command was not delivered, so \
the blocked-command result below would be meaningless; marker held: {allowed_body:?}"
);
assert!(
!blocked_marker.exists(),
"a blocked command must not execute — its marker file must be absent"
);
}
#[test]
fn bash_capability_cache_steers_default_mode() {
let bash = match modern_bash() {
Some(b) => b,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
let bash_ver = match pty_support::bash_version_string(&bash) {
Some(v) => v,
None => {
eprintln!("skipping: could not read $BASH_VERSION");
return;
}
};
let hook = embedded_hook("bash-hook.bash");
let effective_mode =
|verdict: &str, seed_bash_ver: &str, seed_bash: &Path, _tag: &str| -> String {
let env = IsolatedEnv::new();
env.seed_bash_enter_capability(verdict, seed_bash_ver, seed_bash);
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
sess.send_line("printf 'TIRITHMODE<%s>\\n' \"$TIRITH_BASH_EFFECTIVE_MODE\"");
let out = sess.expect_any(&["TIRITHMODE<enter>", "TIRITHMODE<preexec>"], VERDICT_IDLE);
sess.close();
if out.contains("TIRITHMODE<enter>") {
"enter".to_string()
} else if out.contains("TIRITHMODE<preexec>") {
"preexec".to_string()
} else {
format!("unknown: {out}")
}
};
assert_eq!(
effective_mode("broken", &bash_ver, &bash, "broken"),
"preexec",
"a `broken` capability verdict must keep the hook in preexec"
);
assert_eq!(
effective_mode("works", "1.0.0-not-this-bash", &bash, "stale_version"),
"preexec",
"a capability verdict for a different bash version must be ignored as stale"
);
assert_eq!(
effective_mode(
"works",
&bash_ver,
Path::new("/nonexistent/other/bash"),
"stale_path"
),
"preexec",
"a capability verdict for a different bash path must be ignored as stale"
);
assert_eq!(
effective_mode("works", &bash_ver, &bash, "works"),
"enter",
"a `works` capability verdict must make the hook select enter mode"
);
}
#[test]
fn bash_enter_mode_delivers_and_blocks() {
let mut env = IsolatedEnv::new();
let bash = match modern_bash() {
Some(b) => b,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
env.set("TIRITH_BASH_MODE", "enter");
let allowed = env.workdir.join("enter_allowed.txt");
let blocked = env.workdir.join("enter_blocked.txt");
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
sess.send_line("printf 'TIRITHMODE<%s>\\n' \"$TIRITH_BASH_EFFECTIVE_MODE\"");
let mode_out = sess.expect_any(&["TIRITHMODE<enter>", "TIRITHMODE<preexec>"], VERDICT_IDLE);
assert!(
mode_out.contains("TIRITHMODE<enter>"),
"forced enter mode must not degrade at startup, got:\n{mode_out}"
);
sess.clear_buffer();
sess.send_line(&format!("printf 'RAN\\n' >> '{}'", allowed.display()));
let allowed_body = wait_for_marker(&allowed, "RAN", MARKER_MAX);
assert_eq!(
count_occurrences(&allowed_body, "RAN"),
1,
"enter mode must deliver an allowed command exactly once, got: {allowed_body:?}"
);
sess.send_line(&format!(
"printf 'true' | bash && touch '{}'",
blocked.display()
));
sess.wait_idle(QUIET, SETTLE_MAX);
sess.close();
assert!(
!blocked.exists(),
"enter mode must block a pipe-to-interpreter command"
);
}
#[test]
fn bash_enter_mode_protects_vi_keymaps_after_runtime_switch() {
let mut env = IsolatedEnv::new();
let bash = match modern_bash() {
Some(bash) => bash,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
env.set("TIRITH_BASH_MODE", "enter");
let allowed = env.workdir.join("vi_insert_allowed.txt");
let blocked = env.workdir.join("vi_command_blocked.txt");
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line("set -o vi");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line(&format!("printf 'RAN\\n' >> '{}'", allowed.display()));
let allowed_body = wait_for_marker(&allowed, "RAN", MARKER_MAX);
assert_eq!(
count_occurrences(&allowed_body, "RAN"),
1,
"vi-insert Enter must deliver an allowed command exactly once"
);
let dangerous = format!("printf 'true' | bash && touch '{}'", blocked.display());
sess.send_raw(dangerous.as_bytes());
sess.send_raw(b"\x1b\r");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.close();
assert!(
!blocked.exists(),
"vi-command Enter must still pass through Tirith's guarded dispatcher"
);
}
#[test]
fn bash_enter_degradation_restores_custom_ctrl_o_bindings() {
let mut env = IsolatedEnv::new();
let bash = match modern_bash() {
Some(bash) => bash,
None => {
eprintln!("skipping: no modern bash (>= 5) found");
return;
}
};
env.set("TIRITH_BASH_MODE", "enter");
let mut sess = PtySession::spawn(&env, &bash, &["--norc", "--noprofile", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line(
r#"bind -m emacs-standard -x '"\C-o":printf EMACS-C-O'; bind -m vi-insert '"\C-o": "VI-INSERT-C-O"'; bind -m vi-command '"\C-o": "VI-COMMAND-C-O"'"#,
);
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
let hook = embedded_hook("bash-hook.bash");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line("printf 'TIRITH_CTRL_O_MODE<%s>\\n' \"$TIRITH_BASH_EFFECTIVE_MODE\"");
let mode_output = sess.expect_any(
&["TIRITH_CTRL_O_MODE<enter>", "TIRITH_CTRL_O_MODE<preexec>"],
VERDICT_IDLE,
);
assert!(
mode_output.contains("TIRITH_CTRL_O_MODE<enter>"),
"enter mode must remove every captured Ctrl-O bypass before its health gate, got:\n{mode_output}"
);
sess.clear_buffer();
sess.send_line(
r#"if _tirith_bind_x_record_is_ctrl_o '"\C-o" "printf modern"' && _tirith_bind_x_record_is_ctrl_o '"\C-o": "printf legacy"'; then printf 'TIRITH_CTRL_O_RECORDS_%s\n' OK; else printf 'TIRITH_CTRL_O_RECORDS_%s\n' BAD; fi"#,
);
sess.expect("TIRITH_CTRL_O_RECORDS_OK");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line(
r#"bind -m emacs-standard '"\C-o": operate-and-get-next'; if _tirith_startup_health_check; then printf 'TIRITH_CTRL_O_HEALTH_%s\n' BAD; else printf 'TIRITH_CTRL_O_HEALTH_%s\n' OK; fi"#,
);
sess.expect("TIRITH_CTRL_O_HEALTH_OK");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line("_tirith_degrade_to_preexec ctrl-o-test");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line("bind -m emacs-standard -X; bind -m vi-insert -s; bind -m vi-command -s");
let output = sess.expect("VI-COMMAND-C-O");
sess.close();
for expected in ["EMACS-C-O", "VI-INSERT-C-O", "VI-COMMAND-C-O"] {
assert!(
output.contains(expected),
"degradation did not restore {expected}; got:\n{output}"
);
}
}
fn fish_session(env: &mut IsolatedEnv) -> Option<PtySession> {
let fish = fish_bin()?;
let mut sess = PtySession::spawn(env, &fish, &["--no-config", "-i"]);
sess.send_line("function fish_prompt; printf 'TIRITH_PTY> '; end");
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
let hook = embedded_hook("fish-hook.fish");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
sess.send_line("printf 'TIRITH_PROTOCOL=%s\\n' \"$_TIRITH_RECEIPT_PROTOCOL\"");
sess.expect("TIRITH_PROTOCOL=3");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
let (confirmed, unresolved) = strict_ledger_counts(env);
assert_eq!(
confirmed, 0,
"a shell line must not be mislabeled as kernel-confirmed execution"
);
assert!(
unresolved >= 1,
"fish protocol-v3 probe must create durable shell-boundary evidence"
);
}
Some(sess)
}
#[test]
fn fish_allowed_command_executes_exactly_once() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("fish_once.txt");
let mut sess = match fish_session(&mut env) {
Some(s) => s,
None => {
eprintln!("skipping: fish not installed");
return;
}
};
sess.send_line(&format!("printf 'RAN\\n' >> '{}'", marker.display()));
let body = wait_for_marker(&marker, "RAN", MARKER_MAX);
sess.close();
assert_eq!(
count_occurrences(&body, "RAN"),
1,
"fish: allowed command must execute exactly once, marker held: {body:?}"
);
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn fish_rm_tab_completion_does_not_persist_execution() {
let mut env = IsolatedEnv::new();
for name in [
"fish-delete-a.dmg",
"fish-delete-b.zip",
"fish-delete-c.zip",
] {
std::fs::write(env.workdir.join(name), b"fixture").unwrap();
}
let mut sess = match fish_session(&mut env) {
Some(session) => session,
None => {
eprintln!("skipping: fish not installed");
return;
}
};
let ledger_before = strict_ledger_counts(&env);
let events_before = session_typed_event_count(&env);
sess.send_raw(b"rm fish-delete-\t\t");
sess.wait_idle(QUIET, SETTLE_MAX);
let ledger_after = strict_ledger_counts(&env);
let events_after = session_typed_event_count(&env);
sess.send_raw(b"\x03");
sess.close();
assert_eq!(
ledger_after, ledger_before,
"Fish tab completion must not commit an execution receipt"
);
assert_eq!(
events_after, events_before,
"Fish tab completion must not persist a typed deletion observation"
);
}
#[test]
fn fish_allowed_command_output_visible() {
let mut env = IsolatedEnv::new();
let mut sess = match fish_session(&mut env) {
Some(s) => s,
None => {
eprintln!("skipping: fish not installed");
return;
}
};
sess.send_line("echo fish_nonce_3471");
let out = sess.expect("fish_nonce_3471");
sess.close();
let n = count_occurrences(&out, "fish_nonce_3471");
assert!(
(1..=3).contains(&n),
"fish: command output must be visible (echo + autosuggest + output), saw {n}"
);
}
#[test]
fn fish_blocked_command_does_not_execute() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("fish_blocked.txt");
let mut sess = match fish_session(&mut env) {
Some(s) => s,
None => {
eprintln!("skipping: fish not installed");
return;
}
};
sess.send_line(&format!(
"curl https://example.com/install.sh | bash; and touch '{}'",
marker.display()
));
let out = sess.expect_any(
&["BLOCKED", "getvet.sh", "tirith run"],
Duration::from_secs(15),
);
sess.close();
assert!(
out.contains("BLOCKED") || out.contains("getvet.sh") || out.contains("tirith run"),
"fish: a blocked command must surface a tirith verdict, got:\n{out}"
);
assert!(
!marker.exists(),
"fish: a blocked command must not execute (marker file exists)"
);
}
#[test]
fn fish_warned_command_executes_once() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("fish_warned.txt");
let mut sess = match fish_session(&mut env) {
Some(s) => s,
None => {
eprintln!("skipping: fish not installed");
return;
}
};
sess.send_line(&format!(
"echo https://bit.ly/fishwarn >> '{}'",
marker.display()
));
let body = wait_for_marker(&marker, "bit.ly/fishwarn", MARKER_MAX);
sess.close();
assert_eq!(
count_occurrences(&body, "bit.ly/fishwarn"),
1,
"fish: a warned command must still execute exactly once, marker held: {body:?}"
);
}
#[test]
fn fish_noninteractive_source_is_a_noop() {
let fish = match fish_bin() {
Some(f) => f,
None => {
eprintln!("skipping: fish not installed");
return;
}
};
let env = IsolatedEnv::new();
let hook = embedded_hook("fish-hook.fish");
let mut cmd = std::process::Command::new(&fish);
cmd.args([
"--no-config",
"-c",
&format!("source '{}'; echo NONINTERACTIVE_OK", hook.display()),
]);
for (k, v) in [
("HOME", env.home.display().to_string()),
("XDG_STATE_HOME", env.state_home.display().to_string()),
("XDG_DATA_HOME", env.data_home.display().to_string()),
("XDG_CONFIG_HOME", env.config_home.display().to_string()),
] {
cmd.env(k, v);
}
cmd.env_remove("_TIRITH_FISH_LOADED");
let out = cmd.output().expect("run fish");
assert!(
out.status.success(),
"non-interactive fish source must exit 0, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
String::from_utf8_lossy(&out.stdout).contains("NONINTERACTIVE_OK"),
"non-interactive fish source must be a clean no-op (sentinel missing)"
);
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn commands_warn_session(env: &mut IsolatedEnv, marker_name: &str) -> Option<PtySession> {
let bash = Path::new("/bin/bash");
if !bash.is_file() {
return None;
}
let manifest_dir = env.workdir.join(".tirith");
std::fs::create_dir_all(&manifest_dir).expect("create commands manifest directory");
std::fs::write(
manifest_dir.join("commands.yaml"),
format!(
"allowed:\n - name: greet\n command: \"echo https://bit.ly/x > {marker_name}\"\n"
),
)
.expect("write commands manifest");
let policy_root = env.workdir.display().to_string();
env.set("TIRITH_POLICY_ROOT", &policy_root);
env.set("TIRITH_OFFLINE", "1");
let mut sess = PtySession::spawn(env, bash, &["--noprofile", "--norc", "-i"]);
sess.send_line("export PS1='TIRITH_PTY> '");
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
Some(sess)
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn commands_warn_accepts_yes_only_from_foreground_controlling_terminal() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("tty_yes.txt");
let mut sess = commands_warn_session(&mut env, "tty_yes.txt")
.expect("supported Unix test hosts provide /bin/bash");
sess.send_line("tirith commands run greet");
sess.expect("? [y/N] ");
sess.send_line("yes");
let body = wait_for_marker(&marker, "bit.ly/x", MARKER_MAX);
let output = sess.expect("Running allowed command");
sess.close();
assert_eq!(
count_occurrences(&body, "bit.ly/x"),
1,
"foreground controlling-terminal approval must execute exactly once"
);
assert!(
output.contains("shortened_url") || output.contains("WARNING"),
"confirmation must follow a visible warning verdict, got:\n{output}"
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn commands_warn_foreground_no_refuses_without_execution() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("tty_no.txt");
let mut sess = commands_warn_session(&mut env, "tty_no.txt")
.expect("supported Unix test hosts provide /bin/bash");
sess.send_line("tirith commands run greet");
sess.expect("? [y/N] ");
sess.send_line("n");
let output = sess.expect("aborted by user");
sess.close();
assert!(!marker.exists(), "a declined warning must not execute");
assert!(
output.contains("shortened_url") || output.contains("WARNING"),
"decline must retain the warning context, got:\n{output}"
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn commands_warn_background_process_group_cannot_prompt_or_execute() {
let mut env = IsolatedEnv::new();
let marker = env.workdir.join("tty_background.txt");
let mut sess = commands_warn_session(&mut env, "tty_background.txt")
.expect("supported Unix test hosts provide /bin/bash");
sess.send_line("tirith commands run greet &");
let output = sess.expect("not in the controlling terminal foreground process group");
sess.close();
assert!(
output.contains("trusted controlling-terminal confirmation is unavailable"),
"background refusal must explain the trusted-terminal boundary, got:\n{output}"
);
assert!(
!marker.exists(),
"a background process group must not gain confirmation or execute"
);
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn zsh_session(env: &mut IsolatedEnv) -> Option<PtySession> {
let zsh = zsh_bin()?;
let mut sess = PtySession::spawn(env, &zsh, &["-f", "-i"]);
sess.send_line("PROMPT='TIRITH_PTY> '; RPROMPT=''");
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
let hook = embedded_hook("zsh-hook.zsh");
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
sess.send_line("print -r -- \"TIRITH_PROTOCOL=$_TIRITH_RECEIPT_PROTOCOL\"");
sess.expect("TIRITH_PROTOCOL=3");
sess.wait_idle(QUIET, SETTLE_MAX);
sess.clear_buffer();
let (confirmed, unresolved) = strict_ledger_counts(env);
assert_eq!(
confirmed, 0,
"a shell line must not be mislabeled as kernel-confirmed execution"
);
assert!(
unresolved >= 1,
"zsh protocol-v3 probe must create durable shell-boundary evidence"
);
Some(sess)
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn zsh_rc_file_registration_survives_suppressed_exec_optimization() {
let mut env = IsolatedEnv::new();
let zsh = match zsh_bin() {
Some(zsh) => zsh,
None => {
eprintln!("skipping: zsh not installed");
return;
}
};
let hook = embedded_hook("zsh-hook.zsh");
let zdotdir = env.workdir.join("zdot");
std::fs::create_dir_all(&zdotdir).expect("create ZDOTDIR");
std::fs::write(
zdotdir.join(".zshrc"),
format!(
"TRAPWINCH() {{ :; }}\nPROMPT='TIRITH_PTY> '; RPROMPT=''\nsource '{}'\n",
hook.display()
),
)
.expect("write .zshrc");
env.set("ZDOTDIR", &zdotdir.display().to_string());
let mut sess = PtySession::spawn(&env, &zsh, &["-d", "-i"]);
sess.expect("TIRITH_PTY> ");
sess.wait_idle(QUIET, SETTLE_MAX);
let startup = sess.output().to_string();
assert!(
!startup.contains("legacy mode"),
"rc-file hook init must not degrade to legacy mode, got:\n{startup}"
);
sess.clear_buffer();
sess.send_line("print -r -- \"TIRITH_RC_PROTOCOL=$_TIRITH_RECEIPT_PROTOCOL\"");
sess.expect("TIRITH_RC_PROTOCOL=3");
sess.close();
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn zsh_noclobber_keeps_protocol_v3_registration() {
let env = IsolatedEnv::new();
let zsh = match zsh_bin() {
Some(zsh) => zsh,
None => {
eprintln!("skipping: zsh not installed");
return;
}
};
let hook = embedded_hook("zsh-hook.zsh");
let mut sess = PtySession::spawn(&env, &zsh, &["-f", "-i"]);
sess.send_line("PROMPT='TIRITH_PTY> '; RPROMPT=''; setopt NOCLOBBER");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line("print -r -- \"TIRITH_NOCLOBBER_PROTOCOL<$_TIRITH_RECEIPT_PROTOCOL>\"");
let output = sess.expect("TIRITH_NOCLOBBER_PROTOCOL<3>");
sess.close();
assert!(
output.contains("TIRITH_NOCLOBBER_PROTOCOL<3>"),
"zsh noclobber must not disable receipt registration, got:\n{output}"
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn zsh_err_exit_survives_registration_rejection() {
let env = IsolatedEnv::new();
let zsh = match zsh_bin() {
Some(zsh) => zsh,
None => {
eprintln!("skipping: zsh not installed");
return;
}
};
let hook = embedded_hook("zsh-hook.zsh");
let mut sess = PtySession::spawn(&env, &zsh, &["-f", "-i"]);
sess.send_line("PROMPT='TIRITH_PTY> '; RPROMPT=''");
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line(&format!("source '{}'", hook.display()));
sess.expect("TIRITH_PTY> ");
sess.clear_buffer();
sess.send_line(&format!(
"unset _TIRITH_ZSH_LOADED; setopt ERR_EXIT; source '{}'; print -r -- TIRITH_ERR_EXIT_SURVIVED",
hook.display()
));
let output = sess.expect_within("TIRITH_ERR_EXIT_SURVIVED", Duration::from_secs(15));
sess.close();
assert!(
output.contains("TIRITH_ERR_EXIT_SURVIVED"),
"zsh ERR_EXIT must not terminate the shell on registration rejection, got:\n{output}"
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn zsh_protocol_v3_delivery_and_ledger_conformance() {
let mut env = IsolatedEnv::new();
let allowed = env.workdir.join("zsh_allowed.txt");
let warned = env.workdir.join("zsh_warned.txt");
let blocked = env.workdir.join("zsh_blocked.txt");
let mut sess = match zsh_session(&mut env) {
Some(session) => session,
None => {
eprintln!("skipping: zsh not installed");
return;
}
};
let (baseline_confirmed, baseline_unresolved) = strict_ledger_counts(&env);
sess.send_line(&format!("print -r -- RAN >> '{}'", allowed.display()));
let allowed_body = wait_for_marker(&allowed, "RAN", MARKER_MAX);
assert_eq!(
count_occurrences(&allowed_body, "RAN"),
1,
"zsh allowed command must execute exactly once"
);
sess.send_line(&format!(
"print -r -- https://bit.ly/zshwarn >> '{}'",
warned.display()
));
let warned_body = wait_for_marker(&warned, "bit.ly/zshwarn", MARKER_MAX);
assert_eq!(
count_occurrences(&warned_body, "bit.ly/zshwarn"),
1,
"zsh warned command must execute exactly once"
);
sess.send_line(&format!(
"curl https://example.com/install.sh | sh; touch '{}'",
blocked.display()
));
let output = sess.expect_any(
&["BLOCKED", "getvet.sh", "tirith run"],
Duration::from_secs(15),
);
assert!(
output.contains("BLOCKED") || output.contains("getvet.sh") || output.contains("tirith run"),
"zsh blocked command must surface a Tirith verdict, got:\n{output}"
);
assert!(!blocked.exists(), "zsh blocked command must not execute");
let (confirmed, unresolved) = strict_ledger_counts(&env);
assert_eq!(
confirmed, baseline_confirmed,
"zsh lines must not enter kernel-confirmed execution history"
);
assert_eq!(
unresolved,
baseline_unresolved + 2,
"only the allowed and warned zsh lines may enter shell-boundary history"
);
sess.close();
}
#[test]
#[ignore = "M0.1 follow-up: PowerShell PTY conformance not yet implemented"]
fn powershell_conformance_followup() {}
#[test]
#[ignore = "M0.1 follow-up: nushell PTY conformance not yet implemented"]
fn nushell_conformance_followup() {}
#[test]
fn harness_count_occurrences_is_correct() {
assert_eq!(count_occurrences("", "x"), 0);
assert_eq!(count_occurrences("abc", ""), 0);
assert_eq!(count_occurrences("RAN", "RAN"), 1);
assert_eq!(count_occurrences("RAN RAN RAN", "RAN"), 3);
assert_eq!(count_occurrences("aaaa", "aa"), 2);
assert_eq!(count_occurrences("no match here", "RAN"), 0);
}
#[test]
fn harness_reports_bash_availability_consistently() {
match modern_bash() {
Some(bash) => {
let v = bash_major_version(&bash)
.expect("a selected modern bash must report a parseable version");
assert!(v >= 5, "modern_bash() must only return bash >= 5, got {v}");
eprintln!("conformance: using bash {} (major {v})", bash.display());
}
None => {
eprintln!(
"conformance: no modern bash (>= 5) found — bash tests will skip. \
Install one or run with PATH=/opt/homebrew/bin:$PATH"
);
}
}
}