subx_cli/services/ai/
security.rs1use url::Url;
7
8pub fn warn_on_insecure_http(url: &Url, api_key: &str) {
15 if url.scheme() != "http" {
16 return;
17 }
18 if api_key.trim().is_empty() {
19 return;
20 }
21 let host = url.host_str().unwrap_or("");
22 let is_loopback = matches!(host, "127.0.0.1" | "::1" | "localhost");
23 if is_loopback {
24 return;
25 }
26 log::warn!(
27 "AI endpoint uses plaintext HTTP ({}). API key will be transmitted unencrypted; consider using HTTPS.",
28 host
29 );
30}
31
32pub fn warn_on_insecure_http_str(url_str: &str, api_key: &str) {
37 if let Ok(url) = Url::parse(url_str) {
38 warn_on_insecure_http(&url, api_key);
39 }
40}
41
42pub fn local_provider_hint() -> &'static str {
57 "If you intended to call an OpenAI-compatible local or LAN endpoint, \
58 set `ai.provider = \"local\"` (or `ollama`) and configure `ai.base_url` \
59 to your endpoint."
60}
61
62#[cfg(test)]
63mod local_provider_hint_tests {
64 use super::local_provider_hint;
65
66 #[test]
67 fn hint_is_non_empty_and_mentions_local_and_ollama() {
68 let hint = local_provider_hint();
69 assert!(!hint.is_empty(), "hint must not be empty");
70 assert!(hint.contains("local"), "hint must mention `local`: {hint}");
71 assert!(
72 hint.contains("ollama"),
73 "hint must mention `ollama`: {hint}"
74 );
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn https_never_warns() {
84 let url = Url::parse("https://api.example.com/v1").unwrap();
85 warn_on_insecure_http(&url, "sk-secret");
86 }
87
88 #[test]
89 fn http_loopback_does_not_warn() {
90 for host in [
91 "http://127.0.0.1:8080",
92 "http://localhost/v1",
93 "http://[::1]/",
94 ] {
95 let url = Url::parse(host).unwrap();
96 warn_on_insecure_http(&url, "sk-secret");
97 }
98 }
99
100 #[test]
101 fn http_public_with_empty_key_does_not_warn() {
102 let url = Url::parse("http://api.example.com/v1").unwrap();
103 warn_on_insecure_http(&url, "");
104 warn_on_insecure_http(&url, " ");
105 }
106
107 #[test]
108 fn http_public_with_key_runs_without_panic() {
109 let url = Url::parse("http://api.example.com/v1").unwrap();
112 warn_on_insecure_http(&url, "sk-secret");
113 }
114}