mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(models): skip credential files whose access token is not a string (#5864)
* fix(models): skip credential files whose access token is not a string * fix(models): normalize the file-route access token and guard the refresh token
This commit is contained in:
parent
c368a9f229
commit
e2feca1e56
@ -156,8 +156,14 @@ def _extract_claude_code_credential(data: dict[str, Any], source: str) -> Claude
|
||||
logger.debug("Claude Code credentials source %s has a non-object claudeAiOauth container; skipping", source)
|
||||
return None
|
||||
access_token = oauth.get("accessToken", "")
|
||||
if isinstance(access_token, str):
|
||||
# The env and descriptor handoffs strip before they test the token, so a blank or
|
||||
# padded value must not become a credential here either.
|
||||
access_token = access_token.strip()
|
||||
else:
|
||||
access_token = ""
|
||||
if not access_token:
|
||||
logger.debug("Claude Code credentials container exists but no accessToken found")
|
||||
logger.debug("Claude Code credentials container exists but no string accessToken found")
|
||||
return None
|
||||
|
||||
expires_at = oauth.get("expiresAt", 0)
|
||||
@ -165,9 +171,14 @@ def _extract_claude_code_credential(data: dict[str, Any], source: str) -> Claude
|
||||
logger.debug("Claude Code credentials source %s has a non-numeric expiresAt; skipping", source)
|
||||
return None
|
||||
|
||||
refresh_token = oauth.get("refreshToken", "")
|
||||
if not isinstance(refresh_token, str):
|
||||
logger.debug("Claude Code credentials source %s has a non-string refreshToken; using no refresh token", source)
|
||||
refresh_token = ""
|
||||
|
||||
cred = ClaudeCodeCredential(
|
||||
access_token=access_token,
|
||||
refresh_token=oauth.get("refreshToken", ""),
|
||||
refresh_token=refresh_token,
|
||||
expires_at=expires_at,
|
||||
source=source,
|
||||
)
|
||||
@ -244,8 +255,8 @@ def load_codex_cli_credential() -> CodexCliCredential | None:
|
||||
if not isinstance(account_id, str):
|
||||
logger.debug("Codex CLI credentials file has a non-string account_id; using no account")
|
||||
account_id = ""
|
||||
if not access_token:
|
||||
logger.debug("Codex CLI credentials file exists but no token found")
|
||||
if not isinstance(access_token, str) or not access_token:
|
||||
logger.debug("Codex CLI credentials file exists but no string token found")
|
||||
return None
|
||||
|
||||
logger.info("Loaded Codex CLI credential")
|
||||
|
||||
@ -322,6 +322,91 @@ def test_load_claude_code_credential_ignores_non_numeric_expires_at(tmp_path, mo
|
||||
assert load_claude_code_credential() is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"access_token",
|
||||
[
|
||||
12345,
|
||||
{"token": "sk-ant-oat01-nested"},
|
||||
["sk-ant-oat01-list"],
|
||||
],
|
||||
)
|
||||
def test_load_claude_code_credential_ignores_non_string_access_token(tmp_path, monkeypatch, access_token):
|
||||
_clear_claude_code_env(monkeypatch)
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
cred_file = tmp_path / "credentials.json"
|
||||
cred_file.write_text(
|
||||
json.dumps({"claudeAiOauth": {"accessToken": access_token, "expiresAt": 4_102_444_800_000}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("CLAUDE_CODE_CREDENTIALS_PATH", str(cred_file))
|
||||
|
||||
assert load_claude_code_credential() is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("access_token", [" ", "\t\n", " " * 12])
|
||||
def test_load_claude_code_credential_ignores_blank_string_access_token(tmp_path, monkeypatch, access_token):
|
||||
_clear_claude_code_env(monkeypatch)
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
cred_file = tmp_path / "credentials.json"
|
||||
cred_file.write_text(
|
||||
json.dumps({"claudeAiOauth": {"accessToken": access_token, "expiresAt": 4_102_444_800_000}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("CLAUDE_CODE_CREDENTIALS_PATH", str(cred_file))
|
||||
|
||||
assert load_claude_code_credential() is None
|
||||
|
||||
|
||||
def test_load_claude_code_credential_strips_a_padded_access_token(tmp_path, monkeypatch):
|
||||
_clear_claude_code_env(monkeypatch)
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
cred_file = tmp_path / "credentials.json"
|
||||
cred_file.write_text(
|
||||
json.dumps({"claudeAiOauth": {"accessToken": " sk-ant-oat01-padded \n", "expiresAt": 4_102_444_800_000}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("CLAUDE_CODE_CREDENTIALS_PATH", str(cred_file))
|
||||
|
||||
cred = load_claude_code_credential()
|
||||
|
||||
assert cred is not None
|
||||
assert cred.access_token == "sk-ant-oat01-padded"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"refresh_token",
|
||||
[
|
||||
12345,
|
||||
None,
|
||||
{"token": "sk-ant-ort01-nested"},
|
||||
["sk-ant-ort01-list"],
|
||||
],
|
||||
)
|
||||
def test_load_claude_code_credential_degrades_non_string_refresh_token(tmp_path, monkeypatch, refresh_token):
|
||||
_clear_claude_code_env(monkeypatch)
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
cred_file = tmp_path / "credentials.json"
|
||||
cred_file.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"claudeAiOauth": {
|
||||
"accessToken": "sk-ant-oat01-test",
|
||||
"refreshToken": refresh_token,
|
||||
"expiresAt": 4_102_444_800_000,
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("CLAUDE_CODE_CREDENTIALS_PATH", str(cred_file))
|
||||
|
||||
cred = load_claude_code_credential()
|
||||
|
||||
assert cred is not None
|
||||
assert cred.access_token == "sk-ant-oat01-test"
|
||||
assert cred.refresh_token == ""
|
||||
|
||||
|
||||
def test_load_claude_code_credential_falls_back_to_default_when_override_expires_at_is_non_numeric(tmp_path, monkeypatch):
|
||||
_clear_claude_code_env(monkeypatch)
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
@ -356,6 +441,40 @@ def test_load_claude_code_credential_falls_back_to_default_when_override_expires
|
||||
assert cred.source == "claude-cli-file"
|
||||
|
||||
|
||||
def test_load_claude_code_credential_falls_back_to_default_when_override_access_token_is_non_string(tmp_path, monkeypatch):
|
||||
_clear_claude_code_env(monkeypatch)
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
|
||||
override_path = tmp_path / "credentials.json"
|
||||
override_path.write_text(
|
||||
json.dumps({"claudeAiOauth": {"accessToken": 12345, "expiresAt": 4_102_444_800_000}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("CLAUDE_CODE_CREDENTIALS_PATH", str(override_path))
|
||||
|
||||
default_path = tmp_path / ".claude" / ".credentials.json"
|
||||
default_path.parent.mkdir()
|
||||
default_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"claudeAiOauth": {
|
||||
"accessToken": "sk-ant-oat01-default",
|
||||
"refreshToken": "sk-ant-ort01-default",
|
||||
"expiresAt": 4_102_444_800_000,
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
cred = load_claude_code_credential()
|
||||
|
||||
assert cred is not None
|
||||
assert cred.access_token == "sk-ant-oat01-default"
|
||||
assert cred.refresh_token == "sk-ant-ort01-default"
|
||||
assert cred.source == "claude-cli-file"
|
||||
|
||||
|
||||
def test_load_codex_cli_credential_supports_nested_tokens_shape(tmp_path, monkeypatch):
|
||||
auth_path = tmp_path / "auth.json"
|
||||
auth_path.write_text(
|
||||
@ -448,3 +567,21 @@ def test_load_codex_cli_credential_ignores_non_string_account_id(tmp_path, monke
|
||||
assert cred is not None
|
||||
assert cred.access_token == "codex-access-token"
|
||||
assert cred.account_id == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize("access_token", [999, {"nested": "token"}, ["token-list"]])
|
||||
def test_load_codex_cli_credential_ignores_non_string_access_token(tmp_path, monkeypatch, access_token):
|
||||
auth_path = tmp_path / "auth.json"
|
||||
auth_path.write_text(json.dumps({"access_token": access_token, "account_id": "acct-1"}))
|
||||
monkeypatch.setenv("CODEX_AUTH_PATH", str(auth_path))
|
||||
|
||||
assert load_codex_cli_credential() is None
|
||||
|
||||
|
||||
def test_codex_chat_model_reports_missing_credential_for_non_string_access_token(tmp_path, monkeypatch):
|
||||
auth_path = tmp_path / "auth.json"
|
||||
auth_path.write_text(json.dumps({"access_token": 999, "account_id": "acct-1"}))
|
||||
monkeypatch.setenv("CODEX_AUTH_PATH", str(auth_path))
|
||||
|
||||
with pytest.raises(ValueError, match="Codex CLI credential not found"):
|
||||
CodexChatModel(model="gpt-5.4")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user