agentmux_srv/backend/account_login_guard.rs
1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! An agent process must not hold the account's cloud login.
5//!
6//! The srv used to put the user's muxbus access token in every agent's env
7//! (`MUXBUS_TOKEN`, with `MUXBUS_COGNITO_DOMAIN`). Nothing in AgentMux reads
8//! it there: the srv makes every cloud call with its own stored login, and
9//! agents reach the cloud through the srv. What the variable did give an
10//! agent is the power to act as the account: publish WAN keys for an install
11//! it made up and sign jekts as any agent name, which then verify
12//! (`SPEC_WAN_JEKT_VERIFICATION_2026_09_24.md` §6.4). That is why a verified
13//! WAN sender couldn't be trusted without a human approving its install.
14//!
15//! [`strip_account_login`] is called on every path that spawns an agent
16//! process, next to `gh_guard::apply_gh_guard`, and removes the variables
17//! wherever they came from — a persisted `cmd:env` from before this change,
18//! or a caller's override. Like `GH_CONFIG_DIR`, they are reserved.
19//!
20//! Limit: agents run as the same OS user as the srv, so one that goes
21//! looking can still read the srv's stored login from disk (§6.5). This
22//! takes the login out of the agent's hands by default; it is not an OS
23//! boundary.
24
25use std::collections::HashMap;
26
27/// The account-login variables no agent env may carry.
28pub const ACCOUNT_LOGIN_VARS: &[&str] = &["MUXBUS_TOKEN", "MUXBUS_COGNITO_DOMAIN"];
29
30/// Remove every [`ACCOUNT_LOGIN_VARS`] entry from `env_vars`.
31pub fn strip_account_login(env_vars: &mut HashMap<String, String>) {
32 for var in ACCOUNT_LOGIN_VARS {
33 env_vars.remove(*var);
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn the_account_login_is_removed_and_nothing_else() {
43 let mut env: HashMap<String, String> = [
44 ("MUXBUS_TOKEN", "eyJ.access"),
45 ("MUXBUS_COGNITO_DOMAIN", "https://auth.example"),
46 ("MUXBUS_AGENT_ID", "agenty"),
47 ("AGENTMUX_AGENT_ID", "agenty"),
48 ]
49 .into_iter()
50 .map(|(k, v)| (k.to_string(), v.to_string()))
51 .collect();
52 strip_account_login(&mut env);
53 assert!(!env.contains_key("MUXBUS_TOKEN"));
54 assert!(!env.contains_key("MUXBUS_COGNITO_DOMAIN"));
55 assert_eq!(env.get("MUXBUS_AGENT_ID").map(String::as_str), Some("agenty"), "routing id stays");
56 assert_eq!(env.len(), 2);
57 }
58
59 #[test]
60 fn an_env_without_it_is_unchanged() {
61 let mut env: HashMap<String, String> = [("PATH".to_string(), "/bin".to_string())].into_iter().collect();
62 strip_account_login(&mut env);
63 assert_eq!(env.len(), 1);
64 }
65}