lilo_rm_core/
spawn_context.rs1use std::ffi::OsString;
2use std::path::PathBuf;
3
4use crate::LaunchEnv;
5
6pub const CALLER_ENV_DENYLIST: &[&str] = &[
11 "CLAUDECODE",
12 "TMUX",
13 "TMUX_PANE",
14 "RTM_SOCKET_PATH",
15 "RTM_DB_PATH",
16 "HELIOY_SESSION_ID",
17 "HELIOY_RUNTIME",
18 "RTM_SESSION_ID",
19 "RTM_RUNTIME_KIND",
20];
21
22pub const CALLER_ENV_DENYLIST_PREFIXES: &[&str] = &["CLAUDE_CODE_", "CLAUDE_PLUGIN_"];
26
27pub fn capture_caller_env() -> Vec<LaunchEnv> {
35 capture_env_from_os(std::env::vars_os())
36}
37
38pub fn capture_env_from_os<I>(iter: I) -> Vec<LaunchEnv>
43where
44 I: IntoIterator<Item = (OsString, OsString)>,
45{
46 capture_env_from(iter.into_iter().map(|(k, v)| {
47 (
48 k.to_string_lossy().into_owned(),
49 v.to_string_lossy().into_owned(),
50 )
51 }))
52}
53
54pub fn capture_env_from<I, K, V>(iter: I) -> Vec<LaunchEnv>
58where
59 I: IntoIterator<Item = (K, V)>,
60 K: Into<String>,
61 V: Into<String>,
62{
63 iter.into_iter()
64 .map(|(k, v)| (k.into(), v.into()))
65 .filter(|(k, _)| !is_denied(k))
66 .map(|(k, v)| LaunchEnv::new(k, v))
67 .collect()
68}
69
70fn is_denied(key: &str) -> bool {
71 if CALLER_ENV_DENYLIST.contains(&key) {
72 return true;
73 }
74 CALLER_ENV_DENYLIST_PREFIXES
75 .iter()
76 .any(|prefix| key.starts_with(prefix))
77}
78
79pub fn capture_caller_cwd() -> std::io::Result<PathBuf> {
81 std::env::current_dir()
82}
83
84pub fn launcher_probe_cwd() -> PathBuf {
89 PathBuf::from("/")
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn denylist_drops_parent_markers() {
98 let env = capture_env_from([
99 ("PATH", "/usr/bin"),
100 ("CLAUDECODE", "1"),
101 ("CLAUDE_CODE_SESSION_ID", "abc"),
102 ("CLAUDE_PLUGIN_DATA", "/tmp"),
103 ("TMUX", "/private/tmp/tmux"),
104 ("TMUX_PANE", "%4"),
105 ("RTM_SOCKET_PATH", "/tmp/rtm.sock"),
106 ("RTM_DB_PATH", "/tmp/rtm.db"),
107 ("HELIOY_SESSION_ID", "session"),
108 ("HELIOY_RUNTIME", "claude"),
109 ("RTM_SESSION_ID", "session"),
110 ("RTM_RUNTIME_KIND", "claude"),
111 ("HELIOY_PAT", "ghp_secret"),
112 ("ANTHROPIC_API_KEY", "sk-secret"),
113 ]);
114 let keys: Vec<&str> = env.iter().map(|e| e.key.as_str()).collect();
115 assert_eq!(keys, vec!["PATH", "HELIOY_PAT", "ANTHROPIC_API_KEY"]);
116 }
117
118 #[test]
119 fn denylist_keeps_user_state() {
120 let env = capture_env_from([
121 ("PATH", "/usr/bin"),
122 ("HOME", "/Users/alphab"),
123 ("LANG", "en_US.UTF-8"),
124 ("MISE_SHELL", "zsh"),
125 ]);
126 assert_eq!(env.len(), 4);
127 }
128
129 #[test]
130 fn capture_env_from_os_tolerates_non_utf8() {
131 use std::os::unix::ffi::OsStringExt;
132
133 let raw_value = OsString::from_vec(vec![b'A', 0xFF, b'B']);
138 let env = capture_env_from_os([(OsString::from("RTM_TEST_BAD_BYTES"), raw_value)]);
139 assert_eq!(env.len(), 1);
140 assert_eq!(env[0].key, "RTM_TEST_BAD_BYTES");
141 assert!(env[0].value.contains('\u{FFFD}'), "{:?}", env[0].value);
142 }
143
144 #[test]
145 fn capture_env_from_os_applies_denylist() {
146 let env = capture_env_from_os([
149 (OsString::from("PATH"), OsString::from("/usr/bin")),
150 (OsString::from("CLAUDECODE"), OsString::from("1")),
151 (
152 OsString::from("CLAUDE_CODE_SESSION_ID"),
153 OsString::from("abc"),
154 ),
155 ]);
156 let keys: Vec<&str> = env.iter().map(|e| e.key.as_str()).collect();
157 assert_eq!(keys, vec!["PATH"]);
158 }
159}