From 5f6d7c06e0896f4c92aebe7bfb01f5a5d21952ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=B0=E5=B0=BC=E9=BE=9F?= <3098584968@qq.com> Date: Fri, 13 Nov 2026 16:32:26 +0800 Subject: [PATCH] fix(doctor): reject placeholder model credentials (#8184) * fix(doctor): reject placeholder model credentials * fix(doctor): reject whitespace-only model keys * docs: drop changelog edit from doctor PR --- README.md | 4 ++++ backend/tests/test_doctor.py | 18 ++++++++++++++++++ scripts/AGENTS.md | 2 ++ scripts/doctor.py | 11 +++++++++-- 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9bfabf238e..31b6b27a52 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ make install make dev ``` +Run `make doctor` after setting model credentials. It reports obvious placeholder +API keys as incomplete configuration; replace them with real keys before testing +model calls. + Open http://localhost:2026. - All configuration options, providers and manual setup: [Configuration](docs/guide/configuration.md) diff --git a/backend/tests/test_doctor.py b/backend/tests/test_doctor.py index c13a621acf..ba9c6737e9 100644 --- a/backend/tests/test_doctor.py +++ b/backend/tests/test_doctor.py @@ -357,6 +357,24 @@ class TestCheckLLMApiKey: assert all(r.fix is not None for r in failed) assert any("OPENAI_API_KEY" in (r.fix or "") for r in failed) + @pytest.mark.parametrize( + "placeholder", + ["your-openai-api-key", "replace-with-your-totokens-api-key", " "], + ) + def test_placeholder_key_is_not_reported_as_ready(self, tmp_path, monkeypatch, placeholder): + cfg = tmp_path / "config.yaml" + cfg.write_text( + "models:\n - name: relay\n use: langchain_openai:ChatOpenAI\n model: gpt-6-luna\n api_key: $RELAY_API_KEY\n", + encoding="utf-8", + ) + monkeypatch.setenv("RELAY_API_KEY", placeholder) + + results = doctor.check_llm_api_key(cfg) + + assert len(results) == 1 + assert results[0].status == "fail" + assert "RELAY_API_KEY" in (results[0].fix or "") + def test_missing_config_returns_empty(self, tmp_path): results = doctor.check_llm_api_key(tmp_path / "config.yaml") assert results == [] diff --git a/scripts/AGENTS.md b/scripts/AGENTS.md index 618abd4865..56537e9477 100644 --- a/scripts/AGENTS.md +++ b/scripts/AGENTS.md @@ -57,6 +57,8 @@ Cases marked for real Compose run only when a `docker` CLI is installed. files mark the env files optional so a fresh checkout still starts and stops. - **Support bundles export no secrets** and show an external runtime home as `{DEER_FLOW_HOME}`. +- **Doctor does not echo model credentials.** Obvious sample key values fail the + model readiness check even when the environment variable is nonempty. ## Start, stop, clean diff --git a/scripts/doctor.py b/scripts/doctor.py index 193e895d26..a51f564d25 100644 --- a/scripts/doctor.py +++ b/scripts/doctor.py @@ -497,6 +497,12 @@ def _memory_backend_config_fix(detail: str) -> str | None: return None +def _looks_like_key_placeholder(value: str) -> bool: + """Recognize sample key values without logging or transmitting them.""" + normalized = value.strip().lower() + return normalized.startswith(("your-", "replace-with-", "sk-your-", "sk-replace-with-")) + + def check_llm_api_key(config_path: Path) -> list[CheckResult]: """Check that each model's env var is set in the environment.""" if not config_path.exists(): @@ -532,14 +538,15 @@ def check_llm_api_key(config_path: Path) -> list[CheckResult]: model_name = model.get("name", "default") for var in env_refs: label = f"{var} set (model: {model_name})" - if os.environ.get(var): + value = os.environ.get(var) + if value and value.strip() and not _looks_like_key_placeholder(value): results.append(CheckResult(label, "ok")) else: results.append( CheckResult( label, "fail", - fix=f"Add {var}= to your .env file", + fix=f"Set {var} to a real key in your .env file", ) ) except Exception as exc: