From cbf64a4079258236ca3f048b46091bbf7fcd0a06 Mon Sep 17 00:00:00 2001 From: NossXBT Date: Wed, 22 Jul 2026 15:31:17 +0700 Subject: [PATCH] fix(security): use hmac.compare_digest for Telegram auth Replace short-circuiting string inequality with constant-time hmac.compare_digest when verifying Telegram initData hashes so a remote timing oracle cannot recover the expected hash byte-by-byte. Guard unequal-length inputs so verification fails closed. Closes #203 Signed-off-by: NossXBT --- quantara/web_app/telegram/utils.py | 8 +++++++- quantara/web_app/tests/test_telegram.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/quantara/web_app/telegram/utils.py b/quantara/web_app/telegram/utils.py index 51b2e6cb..93355d08 100644 --- a/quantara/web_app/telegram/utils.py +++ b/quantara/web_app/telegram/utils.py @@ -53,7 +53,13 @@ def check_telegram_authorization( secret_key, data_check_string.encode(), hashlib.sha256 ).hexdigest() - if hash_value != check_hash: + # Constant-time compare so a remote timing oracle cannot recover the + # expected hash byte-by-byte (see issue #203). Unequal-length inputs + # make compare_digest raise; treat that as a failed verification. + try: + if not hmac.compare_digest(hash_value, check_hash): + return False + except (TypeError, ValueError): return False if expiration_seconds and (int(time.time()) - int(auth_data.get("auth_date", 0))) > expiration_seconds: diff --git a/quantara/web_app/tests/test_telegram.py b/quantara/web_app/tests/test_telegram.py index 685d5462..707b883a 100644 --- a/quantara/web_app/tests/test_telegram.py +++ b/quantara/web_app/tests/test_telegram.py @@ -146,6 +146,22 @@ def test_rejects_expired_payload(self): ) assert ok is False + def test_uses_constant_time_compare(self): + """Hash comparison must use hmac.compare_digest (timing-safe).""" + import hmac as hmac_mod + from web_app.telegram.utils import check_telegram_authorization + + data = self._build_auth(self.BOT_TOKEN) + with patch.object( + hmac_mod, "compare_digest", wraps=hmac_mod.compare_digest + ) as mock_cmp: + assert check_telegram_authorization(self.BOT_TOKEN, data) is True + assert mock_cmp.called + # Both sides must be str hex digests of equal length. + left, right = mock_cmp.call_args[0] + assert isinstance(left, str) and isinstance(right, str) + assert len(left) == len(right) == 64 + # ───────────────────────────────────────────────────────────────────────────── # 2. notifications.send_health_ratio_notification