agentmux_bashwrap/
askpass.rs1use std::time::Duration;
15
16const ENV_SECRET: &str = "AGENTMUX_ASKPASS_SECRET";
17
18const ANSWER_TIMEOUT: Duration = Duration::from_secs(150);
21
22const SUBCOMMANDS: &[&str] = &[
25 "exec",
26 "hook",
27 "precompact",
28 "sessionstart",
29 "help",
30 "--help",
31 "-h",
32 "--version",
33 "-V",
34];
35
36pub fn askpass_prompt(secret: Option<&str>, args: &[String]) -> Option<String> {
39 secret.filter(|s| !s.is_empty())?;
40 if args
41 .first()
42 .is_some_and(|a| SUBCOMMANDS.contains(&a.as_str()))
43 {
44 return None;
45 }
46 Some(args.join(" "))
47}
48
49pub fn run_if_asked() -> Option<i32> {
52 let secret = std::env::var(ENV_SECRET).ok();
53 let args: Vec<String> = std::env::args().skip(1).collect();
54 let prompt = askpass_prompt(secret.as_deref(), &args)?;
55 let secret = secret.unwrap_or_default();
56 let Some(client) = crate::mps_client::WpsClient::from_env() else {
57 eprintln!("agentmux askpass: AGENTMUX_LOCAL_URL / AGENTMUX_AUTH_KEY not set");
58 return Some(1);
59 };
60 let rt = match tokio::runtime::Runtime::new() {
61 Ok(rt) => rt,
62 Err(e) => {
63 eprintln!("agentmux askpass: {e}");
64 return Some(1);
65 }
66 };
67 let hint = std::env::var("SSH_ASKPASS_PROMPT").unwrap_or_default();
69 let body = serde_json::json!({ "secret": secret, "prompt": prompt, "hint": hint });
70 let answer = rt.block_on(client.post_json("/api/v1/askpass", None, &body, ANSWER_TIMEOUT));
71 match answer
72 .ok()
73 .and_then(|v| v.get("answer").and_then(|a| a.as_str()).map(str::to_string))
74 {
75 Some(text) => {
76 println!("{text}");
78 Some(0)
79 }
80 None => Some(1),
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 fn args(a: &[&str]) -> Vec<String> {
89 a.iter().map(|s| s.to_string()).collect()
90 }
91
92 #[test]
93 fn ssh_asking_is_askpass_and_everything_else_is_bashwrap() {
94 assert_eq!(
95 askpass_prompt(Some("amxa_x"), &args(&["asaf@area54's password: "])),
96 Some("asaf@area54's password: ".to_string())
97 );
98 assert_eq!(
99 askpass_prompt(None, &args(&["asaf@area54's password: "])),
100 None,
101 "no secret, no askpass"
102 );
103 assert_eq!(askpass_prompt(Some(""), &args(&["password: "])), None);
104 assert_eq!(
105 askpass_prompt(Some("amxa_x"), &args(&["exec", "--tool-id", "t"])),
106 None,
107 "an inherited secret never turns a subcommand into askpass"
108 );
109 assert_eq!(askpass_prompt(Some("amxa_x"), &args(&["hook"])), None);
110 }
111}