From f56d276122d99cbf190a22484a981fac7276e69c Mon Sep 17 00:00:00 2001 From: xbzz1018 Date: Mon, 28 Sep 2026 16:16:15 +0800 Subject: [PATCH] fix(models): reject blank Codex CLI tokens (#5959) * fix(models): reject blank Codex CLI tokens Signed-off-by: xbzz1018 * test(models): cover Codex token normalization path Signed-off-by: xbzz1018 --------- Signed-off-by: xbzz1018 Co-authored-by: xbzz1018 --- .../deerflow/models/credential_loader.py | 2 + backend/tests/test_cli_auth_providers.py | 38 +++++++++++++++++++ backend/tests/test_credential_loader.py | 20 ++++++++++ 3 files changed, 60 insertions(+) diff --git a/backend/packages/harness/deerflow/models/credential_loader.py b/backend/packages/harness/deerflow/models/credential_loader.py index 1615e8da75..440d38d105 100644 --- a/backend/packages/harness/deerflow/models/credential_loader.py +++ b/backend/packages/harness/deerflow/models/credential_loader.py @@ -252,6 +252,8 @@ def load_codex_cli_credential() -> CodexCliCredential | None: access_token = data.get("access_token") or data.get("token") or tokens.get("access_token", "") account_id = data.get("account_id") or tokens.get("account_id", "") + if isinstance(access_token, str): + access_token = access_token.strip() if not isinstance(account_id, str): logger.debug("Codex CLI credentials file has a non-string account_id; using no account") account_id = "" diff --git a/backend/tests/test_cli_auth_providers.py b/backend/tests/test_cli_auth_providers.py index 00df4b726b..5e70e5ce41 100644 --- a/backend/tests/test_cli_auth_providers.py +++ b/backend/tests/test_cli_auth_providers.py @@ -23,6 +23,44 @@ def test_codex_provider_requires_credentials(monkeypatch): CodexChatModel() +@pytest.mark.parametrize( + "payload", + [ + {"access_token": " "}, + {"token": "\t\n"}, + {"tokens": {"access_token": " \r\n "}}, + ], +) +def test_codex_provider_rejects_blank_file_credentials(tmp_path, monkeypatch, payload): + auth_path = tmp_path / "auth.json" + auth_path.write_text(json.dumps(payload), encoding="utf-8") + monkeypatch.setenv("CODEX_AUTH_PATH", str(auth_path)) + + with pytest.raises(ValueError, match="Codex CLI credential not found"): + CodexChatModel(model="gpt-5.4") + + +def test_codex_provider_uses_normalized_access_token_in_authorization_header(tmp_path, monkeypatch): + auth_path = tmp_path / "auth.json" + auth_path.write_text(json.dumps({"access_token": " codex-access-token\n"}), encoding="utf-8") + monkeypatch.setenv("CODEX_AUTH_PATH", str(auth_path)) + + captured: dict[str, dict] = {} + + def capture_request(self, headers, payload): + captured["headers"] = headers + captured["payload"] = payload + return {"output": [], "usage": {}} + + monkeypatch.setattr(CodexChatModel, "_stream_response", capture_request) + + model = CodexChatModel(model="gpt-5.4") + model._call_codex_api([HumanMessage(content="hello")]) + + assert captured["headers"]["Authorization"] == "Bearer codex-access-token" + assert captured["payload"]["input"] == [{"role": "user", "content": "hello"}] + + def test_codex_provider_concatenates_multiple_system_messages(monkeypatch): monkeypatch.setattr( CodexChatModel, diff --git a/backend/tests/test_credential_loader.py b/backend/tests/test_credential_loader.py index 7fb1072b89..f70f8d7e21 100644 --- a/backend/tests/test_credential_loader.py +++ b/backend/tests/test_credential_loader.py @@ -509,6 +509,26 @@ def test_load_codex_cli_credential_supports_legacy_top_level_shape(tmp_path, mon assert cred.account_id == "" +@pytest.mark.parametrize("access_token", ["", " ", "\t\n"]) +def test_load_codex_cli_credential_ignores_blank_access_token(tmp_path, monkeypatch, access_token): + auth_path = tmp_path / "auth.json" + auth_path.write_text(json.dumps({"access_token": access_token})) + monkeypatch.setenv("CODEX_AUTH_PATH", str(auth_path)) + + assert load_codex_cli_credential() is None + + +def test_load_codex_cli_credential_strips_access_token_whitespace(tmp_path, monkeypatch): + auth_path = tmp_path / "auth.json" + auth_path.write_text(json.dumps({"access_token": " codex-access-token\n"})) + monkeypatch.setenv("CODEX_AUTH_PATH", str(auth_path)) + + cred = load_codex_cli_credential() + + assert cred is not None + assert cred.access_token == "codex-access-token" + + @pytest.mark.parametrize("payload", [[], "codex-access-token", 5]) def test_load_codex_cli_credential_ignores_non_object_auth_file(tmp_path, monkeypatch, payload): auth_path = tmp_path / "auth.json"