From d5747ad6c205721de1f457ed9f997cc8dc818bf7 Mon Sep 17 00:00:00 2001 From: mnajafian-nv Date: Mon, 3 Aug 2026 11:09:43 -0700 Subject: [PATCH 1/2] fix: reject invalid upstream shared config keys Signed-off-by: mnajafian-nv --- crates/cli/src/configuration/mod.rs | 16 +++++++-- crates/cli/src/diagnostics/mod.rs | 36 ++++++++++++++++++- .../cli/tests/coverage/shared/config_tests.rs | 32 +++++++++++++++++ .../cli/tests/coverage/shared/doctor_tests.rs | 15 ++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/crates/cli/src/configuration/mod.rs b/crates/cli/src/configuration/mod.rs index af6e5d850..2200b9c48 100644 --- a/crates/cli/src/configuration/mod.rs +++ b/crates/cli/src/configuration/mod.rs @@ -65,6 +65,7 @@ struct FileGatewayConfig { } #[derive(Debug, Clone, Default, Deserialize)] +#[serde(deny_unknown_fields)] struct FileUpstreamConfig { openai_base_url: Option, openai_auth_header: Option, @@ -1214,9 +1215,7 @@ pub(crate) fn user_config_dir() -> Option { // Applies the typed TOML config model to the resolved runtime config. Missing sections and fields // are ignored, preserving defaults and prior merge layers. fn apply_file_config(resolved: &mut ResolvedConfig, value: toml::Value) -> Result<(), CliError> { - let config: FileConfig = value.try_into().map_err(|error| { - CliError::Config(format!("invalid gateway configuration shape: {error}")) - })?; + let config = parse_file_config(value)?; apply_file_gateway_config(&mut resolved.gateway, config.gateway)?; apply_file_upstream_config(&mut resolved.gateway, config.upstream)?; apply_file_agents_config(&mut resolved.agents, config.agents); @@ -1224,6 +1223,17 @@ fn apply_file_config(resolved: &mut ResolvedConfig, value: toml::Value) -> Resul Ok(()) } +pub(crate) fn validate_shared_config_shape(value: toml::Value) -> Result<(), CliError> { + let _ = parse_file_config(value)?; + Ok(()) +} + +fn parse_file_config(value: toml::Value) -> Result { + value + .try_into() + .map_err(|error| CliError::Config(format!("invalid gateway configuration shape: {error}"))) +} + fn apply_file_gateway_config( gateway: &mut GatewayConfig, config: Option, diff --git a/crates/cli/src/diagnostics/mod.rs b/crates/cli/src/diagnostics/mod.rs index 428f03d39..40c93cdba 100644 --- a/crates/cli/src/diagnostics/mod.rs +++ b/crates/cli/src/diagnostics/mod.rs @@ -274,6 +274,40 @@ fn dynamic_plugin_host_config_check(plugin: &DynamicPluginReferenceInfo) -> Chec } fn layer_status(path: &Path) -> ConfigLayer { + let mut layer = toml_layer_status(path); + if !matches!(layer.status, Status::Pass) { + return layer; + } + let text = match std::fs::read_to_string(path) { + Ok(text) => text, + Err(err) => { + layer.status = Status::Fail; + layer.active = false; + layer.details = format!("unreadable: {err}"); + return layer; + } + }; + let table = match text.parse::() { + Ok(table) => table, + Err(err) => { + layer.status = Status::Fail; + layer.active = false; + layer.details = format!("invalid TOML: {err}"); + return layer; + } + }; + match crate::configuration::validate_shared_config_shape(toml::Value::Table(table)) { + Ok(()) => layer, + Err(err) => ConfigLayer { + path: path.to_path_buf(), + status: Status::Fail, + active: false, + details: err.to_string(), + }, + } +} + +fn toml_layer_status(path: &Path) -> ConfigLayer { if !path.exists() { return ConfigLayer { path: path.to_path_buf(), @@ -323,7 +357,7 @@ fn plugin_layer_status( contributing_paths: &[PathBuf], plugin_error: Option<&str>, ) -> ConfigLayer { - let mut layer = layer_status(path); + let mut layer = toml_layer_status(path); if let Some(error) = plugin_error.filter(|error| error.contains(&path.display().to_string())) && matches!(layer.status, Status::Pass) { diff --git a/crates/cli/tests/coverage/shared/config_tests.rs b/crates/cli/tests/coverage/shared/config_tests.rs index b72d5a956..333650646 100644 --- a/crates/cli/tests/coverage/shared/config_tests.rs +++ b/crates/cli/tests/coverage/shared/config_tests.rs @@ -3429,6 +3429,38 @@ fn malformed_shared_config_reports_context() { assert!(error.contains("invalid gateway configuration shape")); + let invalid_upstream_key = temp.path().join("invalid-upstream-key.toml"); + std::fs::write( + &invalid_upstream_key, + "[upstream]\nopenai_baseurl = \"https://example.test/v1\"\n", + ) + .unwrap(); + let args = GatewayOverrides { + config: Some(invalid_upstream_key), + ..GatewayOverrides::default() + }; + + let error = resolve_server_config(&args).unwrap_err().to_string(); + + assert!(error.contains("invalid gateway configuration shape")); + assert!(error.contains("openai_baseurl")); + + let invalid_nested_upstream = temp.path().join("invalid-nested-upstream.toml"); + std::fs::write( + &invalid_nested_upstream, + "[upstream.openai]\nbase_url = \"https://example.test/v1\"\n", + ) + .unwrap(); + let args = GatewayOverrides { + config: Some(invalid_nested_upstream), + ..GatewayOverrides::default() + }; + + let error = resolve_server_config(&args).unwrap_err().to_string(); + + assert!(error.contains("invalid gateway configuration shape")); + assert!(error.contains("openai")); + let plugin_config = temp.path().join("config-with-invalid-plugins.toml"); std::fs::write(&plugin_config, "").unwrap(); std::fs::write(temp.path().join("plugins.toml"), "version = [").unwrap(); diff --git a/crates/cli/tests/coverage/shared/doctor_tests.rs b/crates/cli/tests/coverage/shared/doctor_tests.rs index a16133830..10cdfeed7 100644 --- a/crates/cli/tests/coverage/shared/doctor_tests.rs +++ b/crates/cli/tests/coverage/shared/doctor_tests.rs @@ -414,6 +414,21 @@ fn layer_status_reports_missing_valid_invalid_and_non_directory_paths() { assert_eq!(valid_layer.status, Status::Pass); assert!(valid_layer.active); + let invalid_shape = temp.path().join("invalid-shape.toml"); + std::fs::write( + &invalid_shape, + "[upstream.openai]\nbase_url = \"http://local\"\n", + ) + .unwrap(); + let invalid_shape_layer = layer_status(&invalid_shape); + assert_eq!(invalid_shape_layer.status, Status::Fail); + assert!( + invalid_shape_layer + .details + .contains("invalid gateway configuration shape") + ); + assert!(invalid_shape_layer.details.contains("openai")); + let invalid = temp.path().join("invalid.toml"); std::fs::write(&invalid, "[upstream\n").unwrap(); let invalid_layer = layer_status(&invalid); From 8ecce76aaa74d22bb665c7bc8e4835609edb6b1c Mon Sep 17 00:00:00 2001 From: mnajafian-nv Date: Mon, 3 Aug 2026 11:37:11 -0700 Subject: [PATCH 2/2] test: assert inactive doctor status for invalid shared config shape Signed-off-by: mnajafian-nv --- crates/cli/tests/coverage/shared/doctor_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cli/tests/coverage/shared/doctor_tests.rs b/crates/cli/tests/coverage/shared/doctor_tests.rs index 10cdfeed7..9d5aa8cb7 100644 --- a/crates/cli/tests/coverage/shared/doctor_tests.rs +++ b/crates/cli/tests/coverage/shared/doctor_tests.rs @@ -422,6 +422,7 @@ fn layer_status_reports_missing_valid_invalid_and_non_directory_paths() { .unwrap(); let invalid_shape_layer = layer_status(&invalid_shape); assert_eq!(invalid_shape_layer.status, Status::Fail); + assert!(!invalid_shape_layer.active); assert!( invalid_shape_layer .details