From 97b79e08c376f893ca136b26f3e51cacb5f91043 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Thu, 1 Oct 2026 01:54:09 +0000 Subject: [PATCH] fix(alpha_vantage): keep the API key out of every error an Alpha Vantage answer raises - rate-limit and invalid-key notices are scrubbed of the key as the rejection message is --- tests/test_alpha_vantage_hardening.py | 17 +++++++++++++++++ .../dataflows/vendors/alpha_vantage/common.py | 9 ++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/test_alpha_vantage_hardening.py b/tests/test_alpha_vantage_hardening.py index 77a828877..716bcbb4f 100644 --- a/tests/test_alpha_vantage_hardening.py +++ b/tests/test_alpha_vantage_hardening.py @@ -86,6 +86,23 @@ def test_a_rejected_request_is_unavailable_not_data(monkeypatch): assert out.startswith("DATA_UNAVAILABLE"), out +@pytest.mark.unit +@pytest.mark.parametrize("body", [ + '{"Note": "Thank you for using key k-secret. API call frequency is 5 calls per minute."}', + '{"Information": "the apikey k-secret is invalid or missing."}', +]) +def test_the_key_never_reaches_an_error_message(monkeypatch, body): + """Alpha Vantage can echo the key in its notices; error text reaches logs and reports.""" + from tradingagents.dataflows.errors import VendorError + + monkeypatch.setenv("ALPHA_VANTAGE_API_KEY", "k-secret") + monkeypatch.setattr(net.requests, "get", _patched_get(body)) + + with pytest.raises(VendorError) as raised: + av._make_api_request("TIME_SERIES_DAILY", {"symbol": "AAPL"}) + assert "k-secret" not in str(raised.value) + + _FUNDAMENTALS_JSON = json.dumps({ "symbol": "AAPL", "annualReports": [ diff --git a/tradingagents/dataflows/vendors/alpha_vantage/common.py b/tradingagents/dataflows/vendors/alpha_vantage/common.py index 225891452..faa09f911 100644 --- a/tradingagents/dataflows/vendors/alpha_vantage/common.py +++ b/tradingagents/dataflows/vendors/alpha_vantage/common.py @@ -109,8 +109,13 @@ def _make_api_request(function_name: str, params: dict) -> dict | str: # genuine rate limit and an invalid/missing key aren't conflated (#991): # rate-limit phrasing is checked first because those notices also mention # "API key" ("your API key ... 25 requests per day"). + def scrubbed(text: str) -> str: + # Alpha Vantage can echo the key in its notices; error text reaches logs. + return text.replace(api_key, "***") if api_key else text + notice = response_json.get("Information") or response_json.get("Note") if notice: + notice = scrubbed(notice) low = notice.lower() if any(m in low for m in ("rate limit", "requests per day", "call frequency", "premium")): raise AlphaVantageRateLimitError(f"Alpha Vantage rate limit exceeded: {notice}") @@ -124,9 +129,7 @@ def _make_api_request(function_name: str, params: dict) -> dict | str: # instrument (#1442). rejection = response_json.get("Error Message") if rejection: - if api_key: - rejection = rejection.replace(api_key, "***") - raise VendorUnavailableError(f"Alpha Vantage rejected the request: {rejection}") + raise VendorUnavailableError(f"Alpha Vantage rejected the request: {scrubbed(rejection)}") return response_text