From 403c19f6734259cc22ef8ac0c90162ffceeec796 Mon Sep 17 00:00:00 2001 From: tbedor-square2 <80363622+tbedor-square2@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:07:26 -0700 Subject: [PATCH] fix(desktop): show OpenAI-compatible endpoint settings in the baked-env UI (#8117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary `is_safe_to_reveal` (`desktop/src-tauri/src/managed_agents/env_vars.rs`) is the single default-deny allowlist for showing baked env values. It lists `DATABRICKS_HOST`/`DATABRICKS_MODEL`/`DATABRICKS_MODEL_FILTER` but not their OpenAI-compatible counterparts, so a build that bakes `OPENAI_COMPAT_BASE_URL`, `OPENAI_COMPAT_MODEL`, `OPENAI_COMPAT_API` shows them as `••••••`. Adds those three non-secret settings to the allowlist: an endpoint URL, a model id, and an `auto|chat|responses` enum. **No secret is revealed: `OPENAI_COMPAT_API_KEY` is deliberately not added and remains masked.** ### Related issue None found. ### Testing - New test `baked_env_openai_compat_endpoint_is_unmasked_but_key_stays_masked` covers the three revealed settings and asserts the API key stays masked; `baked_env_allowlist_is_case_insensitive` extended. - `cargo test --lib baked_env` in `desktop/src-tauri`: 11 passed. `cargo fmt` clean. - Not exercised in the running desktop UI. Signed-off-by: Tom Bedor Co-authored-by: Tom Bedor --- .../src/commands/agent_config_tests.rs | 30 +++++++++++++++++++ .../src-tauri/src/managed_agents/env_vars.rs | 6 ++++ 2 files changed, 36 insertions(+) diff --git a/desktop/src-tauri/src/commands/agent_config_tests.rs b/desktop/src-tauri/src/commands/agent_config_tests.rs index 14e7da098d..3ff6824c37 100644 --- a/desktop/src-tauri/src/commands/agent_config_tests.rs +++ b/desktop/src-tauri/src/commands/agent_config_tests.rs @@ -714,6 +714,31 @@ fn baked_env_thinking_summary_is_unmasked() { assert!(!summary.masked); } +#[test] +fn baked_env_openai_compat_endpoint_is_unmasked_but_key_stays_masked() { + // The OpenAI-compatible endpoint, model id, and API flavor are + // non-secret defaults; the API key next to them is a credential. + let entries = baked_env_from_map(&[ + ("OPENAI_COMPAT_BASE_URL", "https://llm.example/v1"), + ("OPENAI_COMPAT_MODEL", "balanced"), + ("OPENAI_COMPAT_API", "chat"), + ("OPENAI_COMPAT_API_KEY", "placeholder"), + ]); + assert_eq!(entries.len(), 4); + let find = |k: &str| entries.iter().find(|e| e.key == k).unwrap(); + assert_eq!( + find("OPENAI_COMPAT_BASE_URL").value, + "https://llm.example/v1" + ); + assert!(!find("OPENAI_COMPAT_BASE_URL").masked); + assert_eq!(find("OPENAI_COMPAT_MODEL").value, "balanced"); + assert!(!find("OPENAI_COMPAT_MODEL").masked); + assert_eq!(find("OPENAI_COMPAT_API").value, "chat"); + assert!(!find("OPENAI_COMPAT_API").masked); + assert!(find("OPENAI_COMPAT_API_KEY").masked); + assert_eq!(find("OPENAI_COMPAT_API_KEY").value, "••••••"); +} + #[test] fn baked_env_allowlist_is_case_insensitive() { // Known-safe keys — case-insensitive match must allow them. @@ -729,6 +754,11 @@ fn baked_env_allowlist_is_case_insensitive() { assert!(super::is_safe_to_reveal("DATABRICKS_HOST")); assert!(super::is_safe_to_reveal("databricks_model")); assert!(super::is_safe_to_reveal("DATABRICKS_MODEL")); + assert!(super::is_safe_to_reveal("openai_compat_base_url")); + assert!(super::is_safe_to_reveal("OPENAI_COMPAT_BASE_URL")); + assert!(super::is_safe_to_reveal("OPENAI_COMPAT_MODEL")); + assert!(super::is_safe_to_reveal("OPENAI_COMPAT_API")); + assert!(!super::is_safe_to_reveal("OPENAI_COMPAT_API_KEY")); // Keys NOT in the allowlist — masked regardless of naming pattern. assert!(!super::is_safe_to_reveal("my_api_key")); assert!(!super::is_safe_to_reveal("GITHUB_TOKEN")); diff --git a/desktop/src-tauri/src/managed_agents/env_vars.rs b/desktop/src-tauri/src/managed_agents/env_vars.rs index 6b12fbcd2b..2fb74c1af4 100644 --- a/desktop/src-tauri/src/managed_agents/env_vars.rs +++ b/desktop/src-tauri/src/managed_agents/env_vars.rs @@ -181,6 +181,9 @@ pub fn validate_user_env_keys(env_vars: &BTreeMap) -> Result<(), /// - `BUZZ_AGENT_THINKING_EFFORT` — non-secret enum (none/minimal/low/medium/high/xhigh/max) /// - `BUZZ_AGENT_THINKING_SUMMARY` — non-secret enum (auto/concise/detailed) /// - `DATABRICKS_HOST`, `DATABRICKS_MODEL`, `DATABRICKS_MODEL_FILTER` — Block non-secret defaults +/// - `OPENAI_COMPAT_BASE_URL`, `OPENAI_COMPAT_MODEL`, `OPENAI_COMPAT_API` — OpenAI-compatible +/// endpoint, model id, and API flavor (`auto|chat|responses`); the key itself +/// (`OPENAI_COMPAT_API_KEY`) is not listed and stays masked pub(crate) fn is_safe_to_reveal(key: &str) -> bool { const SAFE_KEYS: &[&str] = &[ "BUZZ_AGENT_PROVIDER", @@ -190,6 +193,9 @@ pub(crate) fn is_safe_to_reveal(key: &str) -> bool { "DATABRICKS_HOST", "DATABRICKS_MODEL", "DATABRICKS_MODEL_FILTER", + "OPENAI_COMPAT_BASE_URL", + "OPENAI_COMPAT_MODEL", + "OPENAI_COMPAT_API", ]; let upper = key.to_ascii_uppercase(); SAFE_KEYS.iter().any(|safe| upper == *safe)