From 6480715a165ed8af79bc999ea6df62cfe291f147 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Mon, 20 Apr 2026 01:36:54 -0700 Subject: [PATCH] fix(settings): strip env-supplied ApiKeys from the request before persisting (#9438) GET /api/settings returns settings.ApiKeys as the merged env+runtime list via ApplicationConfig.ToRuntimeSettings(). The WebUI displays that list and round-trips it back on POST /api/settings unchanged. UpdateSettingsEndpoint was then doing: appConfig.ApiKeys = append(envKeys, runtimeKeys...) where runtimeKeys already contained envKeys (because the UI got them from the merged GET). Every save therefore duplicated the env keys on top of the previous merge, and also wrote the duplicates to runtime_settings.json so the duplication survived restarts and compounded with each save. This is the user-visible behaviour in #9071: the Web UI shows the keys twice / three times after consecutive saves. Before we marshal the settings to disk or call ApplyRuntimeSettings, drop any incoming key that already appears in startupConfig.ApiKeys. The file on disk now stores only the genuinely runtime-added keys; the subsequent append(envKeys, runtimeKeys...) produces one copy of each env key, as intended. Behaviour is unchanged for users who never had env keys set. Fixes #9071 Co-authored-by: SAY-5 --- core/http/endpoints/localai/settings.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/http/endpoints/localai/settings.go b/core/http/endpoints/localai/settings.go index 2428c9cd4..0f5210237 100644 --- a/core/http/endpoints/localai/settings.go +++ b/core/http/endpoints/localai/settings.go @@ -110,6 +110,27 @@ func UpdateSettingsEndpoint(app *application.Application) echo.HandlerFunc { }) } + // The UI reads ApiKeys from GET /api/settings, which already returns the + // merged env+runtime list. When the user clicks Save, the same merged + // list comes back in the POST body. Strip the env-supplied keys from + // the incoming list before we persist or re-merge, otherwise each save + // duplicates the env keys on top of the previous merge (#9071). + if settings.ApiKeys != nil { + envKeys := startupConfig.ApiKeys + envSet := make(map[string]struct{}, len(envKeys)) + for _, k := range envKeys { + envSet[k] = struct{}{} + } + runtimeOnly := make([]string, 0, len(*settings.ApiKeys)) + for _, k := range *settings.ApiKeys { + if _, fromEnv := envSet[k]; fromEnv { + continue + } + runtimeOnly = append(runtimeOnly, k) + } + settings.ApiKeys = &runtimeOnly + } + settingsFile := filepath.Join(appConfig.DynamicConfigsDir, "runtime_settings.json") settingsJSON, err := json.MarshalIndent(settings, "", " ") if err != nil {