use anyhow::Result; use std::collections::HashMap; use std::env; use std::fs::File; use std::fs::{self}; use std::io::Write; use std::path::Path; use std::path::PathBuf; pub fn normalize_null_device_env(env_map: &mut HashMap) { let keys: Vec = env_map.keys().cloned().collect(); for k in keys { if let Some(v) = env_map.get(&k).cloned() { let t = v.trim().to_ascii_lowercase(); if t == "/dev/null" || t == "\\\\\\\\dev\\\\\\\\null" { env_map.insert(k, "NUL".to_string()); } } } } pub fn ensure_non_interactive_pager(env_map: &mut HashMap) { env_map .entry("GIT_PAGER".into()) .or_insert_with(|| "more.com".into()); env_map .entry("PAGER".into()) .or_insert_with(|| "more.com".into()); env_map.entry("LESS".into()).or_insert_with(|| "".into()); } fn prepend_path(env_map: &mut HashMap, prefix: &str) { let existing = env_map .get("PATH") .cloned() .or_else(|| env::var("PATH").ok()) .unwrap_or_default(); let parts: Vec = existing.split(';').map(|s| s.to_string()).collect(); if parts .first() .map(|p| p.eq_ignore_ascii_case(prefix)) .unwrap_or(false) { return; } let mut new_path = String::new(); new_path.push_str(prefix); if !existing.is_empty() { new_path.push(';'); new_path.push_str(&existing); } env_map.insert("PATH".into(), new_path); } fn reorder_pathext_for_stubs(env_map: &mut HashMap) { let default = env_map .get("PATHEXT") .cloned() .or_else(|| env::var("PATHEXT").ok()) .unwrap_or(".COM;.EXE;.BAT;.CMD".to_string()); let exts: Vec = default .split(';') .filter(|e| !e.is_empty()) .map(|s| s.to_string()) .collect(); let exts_norm: Vec = exts.iter().map(|e| e.to_ascii_uppercase()).collect(); let want = [".BAT", ".CMD"]; // move to front if present let mut front: Vec = Vec::new(); for w in want { if let Some(idx) = exts_norm.iter().position(|e| e == w) { front.push(exts[idx].clone()); } } let rest: Vec = exts .into_iter() .enumerate() .filter(|(i, _)| { let up = &exts_norm[*i]; up != ".BAT" && up != ".CMD" }) .map(|(_, e)| e) .collect(); let mut combined = Vec::new(); combined.extend(front); combined.extend(rest); env_map.insert("PATHEXT".into(), combined.join(";")); } fn ensure_denybin(tools: &[&str], denybin_dir: Option<&Path>) -> Result { let base = match denybin_dir { Some(p) => p.to_path_buf(), None => { let home = dirs_next::home_dir().ok_or_else(|| anyhow::anyhow!("no home dir"))?; home.join(".sbx-denybin") } }; fs::create_dir_all(&base)?; for tool in tools { for ext in [".bat", ".cmd"] { let path = base.join(format!("{}{}", tool, ext)); if !path.exists() { let mut f = File::create(&path)?; f.write_all(b"@echo off\\r\\nexit /b 1\\r\\n")?; } } } Ok(base) } pub fn apply_no_network_to_env(env_map: &mut HashMap) -> Result<()> { env_map.insert("SBX_NONET_ACTIVE".into(), "1".into()); env_map .entry("HTTP_PROXY".into()) .or_insert_with(|| "http://127.0.0.1:9".into()); env_map .entry("HTTPS_PROXY".into()) .or_insert_with(|| "http://127.0.0.1:9".into()); env_map .entry("ALL_PROXY".into()) .or_insert_with(|| "http://127.0.0.1:9".into()); env_map .entry("NO_PROXY".into()) .or_insert_with(|| "localhost,127.0.0.1,::1".into()); env_map .entry("PIP_NO_INDEX".into()) .or_insert_with(|| "1".into()); env_map .entry("PIP_DISABLE_PIP_VERSION_CHECK".into()) .or_insert_with(|| "1".into()); env_map .entry("NPM_CONFIG_OFFLINE".into()) .or_insert_with(|| "true".into()); env_map .entry("CARGO_NET_OFFLINE".into()) .or_insert_with(|| "true".into()); env_map .entry("GIT_HTTP_PROXY".into()) .or_insert_with(|| "http://127.0.0.1:9".into()); env_map .entry("GIT_HTTPS_PROXY".into()) .or_insert_with(|| "http://127.0.0.1:9".into()); env_map .entry("GIT_SSH_COMMAND".into()) .or_insert_with(|| "cmd /c exit 1".into()); env_map .entry("GIT_ALLOW_PROTOCOLS".into()) .or_insert_with(|| "".into()); // Block interactive network tools that bypass HTTP(S) proxy settings, but // allow curl/wget to run so commands like `curl --version` still succeed. // Network access is disabled via proxy envs above. let base = ensure_denybin(&["ssh", "scp"], None)?; // Clean up any stale stubs from previous runs so real curl/wget can run. for tool in ["curl", "wget"] { for ext in [".bat", ".cmd"] { let p = base.join(format!("{}{}", tool, ext)); if p.exists() { let _ = std::fs::remove_file(&p); } } } prepend_path(env_map, &base.to_string_lossy()); reorder_pathext_for_stubs(env_map); Ok(()) }