Author SHA1 Message Date
root 7e4d1ffc29 scrum: validate API key presence when enabling cloud provider
admin_save_config silently accepted enabled=True for any provider even
when no api_key was configured (neither in the request body, nor already
stored, nor available in the provider's env var). Result: /api/admin/config
returned 200, the provider was marked enabled in UI and config, then every
actual query to that provider 401'd at call time with an opaque error.

The fix: when the incoming patch sets enabled=True, compute the effective
key (request body preferred, falling back to stored config, falling back
to the provider's env var via get_api_key()) and reject with 400 if the
effective key is empty. Keeps all existing behavior for disabled providers
and for updates that don't touch enabled.

Surfaced by lakehouse scrum-master pipeline run 2026-04-24 (finding F5).
2026-04-24 06:14:25 -05:00
+15 -3
View File
@@ -7064,6 +7064,18 @@ def admin_save_config():
new_key = prov.get("api_key", "")
if not new_key:
prov["api_key"] = cfg["providers"][name].get("api_key", "")
# Enabling a cloud provider requires a usable key (incoming,
# stored, or env). Prevents silent auth failures where
# `enabled=True` is saved with no key and queries 401 at
# call time. get_api_key() checks config + env fallback.
if prov.get("enabled") is True:
effective_key = prov.get("api_key") or get_api_key(name)
if not effective_key:
return jsonify({
"error": f"Cannot enable provider '{name}' without an API key. "
f"Provide api_key in the request, save a key first, "
f"or set the environment variable."
}), 400
cfg["providers"][name].update(prov)
if "disabled_models" in data:
cfg["disabled_models"] = data["disabled_models"]
@@ -9956,7 +9968,7 @@ def _run_meta_pipeline(pipeline_id):
stages = pipe["stages"] or ["extract", "research", "validate", "synthesize"]
data_source = pipe["data_source"]
config = pipe["config"] or {}
model_sets = config.get("model_sets", [["qwen2.5:latest"], ["ollama_cloud::gpt-oss:120b"], ["gemma2:latest"]])
model_sets = config.get("model_sets", [["qwen2.5:latest"], ["mistral:latest"], ["gemma2:latest"]])
max_iterations = config.get("max_iterations", len(model_sets))
_meta_status[pipeline_id] = {"stage": 0, "substep": "Gathering data...", "progress": 0, "iteration": 0}
@@ -10081,7 +10093,7 @@ def create_meta_pipeline():
all_m = [m["name"] for m in resp.json().get("models", []) if m["size"] > 1e9]
models = [[m] for m in all_m[:4]]
except Exception:
models = [["qwen2.5:latest"], ["ollama_cloud::gpt-oss:120b"]]
models = [["qwen2.5:latest"], ["mistral:latest"]]
config = {"model_sets": models, "max_iterations": len(models)}
with get_db() as conn:
@@ -11832,7 +11844,7 @@ def run_refine(config):
start = time.time() * 1000
prompt = config["prompt"]
orchestrator = config.get("orchestrator", "qwen2.5:latest")
workers = config.get("models", ["qwen2.5:latest", "ollama_cloud::gpt-oss:120b"])
workers = config.get("models", ["qwen2.5:latest", "mistral:latest"])
max_stages = config.get("max_stages", 5)
yield sse({"type": "clear"})
steps = []