use url::Url;
pub fn warn_on_insecure_http(url: &Url, api_key: &str) {
if url.scheme() != "http" {
return;
}
if api_key.trim().is_empty() {
return;
}
let host = url.host_str().unwrap_or("");
let is_loopback = matches!(host, "127.0.0.1" | "::1" | "localhost");
if is_loopback {
return;
}
log::warn!(
"AI endpoint uses plaintext HTTP ({}). API key will be transmitted unencrypted; consider using HTTPS.",
host
);
}
pub fn warn_on_insecure_http_str(url_str: &str, api_key: &str) {
if let Ok(url) = Url::parse(url_str) {
warn_on_insecure_http(&url, api_key);
}
}
pub fn local_provider_hint() -> &'static str {
"If you intended to call an OpenAI-compatible local or LAN endpoint, \
set `ai.provider = \"local\"` (or `ollama`) and configure `ai.base_url` \
to your endpoint."
}
#[cfg(test)]
mod local_provider_hint_tests {
use super::local_provider_hint;
#[test]
fn hint_is_non_empty_and_mentions_local_and_ollama() {
let hint = local_provider_hint();
assert!(!hint.is_empty(), "hint must not be empty");
assert!(hint.contains("local"), "hint must mention `local`: {hint}");
assert!(
hint.contains("ollama"),
"hint must mention `ollama`: {hint}"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn https_never_warns() {
let url = Url::parse("https://api.example.com/v1").unwrap();
warn_on_insecure_http(&url, "sk-secret");
}
#[test]
fn http_loopback_does_not_warn() {
for host in [
"http://127.0.0.1:8080",
"http://localhost/v1",
"http://[::1]/",
] {
let url = Url::parse(host).unwrap();
warn_on_insecure_http(&url, "sk-secret");
}
}
#[test]
fn http_public_with_empty_key_does_not_warn() {
let url = Url::parse("http://api.example.com/v1").unwrap();
warn_on_insecure_http(&url, "");
warn_on_insecure_http(&url, " ");
}
#[test]
fn http_public_with_key_runs_without_panic() {
let url = Url::parse("http://api.example.com/v1").unwrap();
warn_on_insecure_http(&url, "sk-secret");
}
}