From 349ef4ca420102aa2cc39b183667d23726d88673 Mon Sep 17 00:00:00 2001 From: yryzhan-vitech Date: Sun, 26 Jul 2026 16:20:42 +0200 Subject: [PATCH] test(logging): move sanitizer tests into mapped core-utils file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codecov reported 75% patch coverage because the tests lived in tests/logging_callback_tests/ (the Integrations shard, which does not upload coverage for this source file). Move the 9 tests into tests/test_litellm/litellm_core_utils/test_litellm_logging.py — the mapped file for litellm_core_utils/litellm_logging.py, run by the coverage-uploading core-utils shard — so patch coverage reflects them. --- .../test_standard_logging_payload.py | 111 --------------- .../test_litellm_logging.py | 128 ++++++++++++++++++ 2 files changed, 128 insertions(+), 111 deletions(-) diff --git a/tests/logging_callback_tests/test_standard_logging_payload.py b/tests/logging_callback_tests/test_standard_logging_payload.py index 5354fedc250..f29b245b3be 100644 --- a/tests/logging_callback_tests/test_standard_logging_payload.py +++ b/tests/logging_callback_tests/test_standard_logging_payload.py @@ -1169,114 +1169,3 @@ def test_merge_litellm_metadata_bedrock_passthrough_scenario(): # Verify total number of fields (9 user fields + 4 model fields = 13) assert len(result) == 13 - - -def test_get_standard_logging_metadata_raw_sk_key_in_hash_is_hashed(): - """ - SECURITY: a raw virtual key (`sk-...`) must never be persisted under - `user_api_key_hash`. It should be SHA-256 hashed before it can reach any - logging sink (e.g. the S3 request/llm logs). - """ - import hashlib - - raw_key = "sk-1234567890abcdefghijklmnop" - metadata = {"user_api_key_hash": raw_key} - result = StandardLoggingPayloadSetup.get_standard_logging_metadata(metadata) - all_fields_present(result) - assert result["user_api_key_hash"] != raw_key - assert result["user_api_key_hash"] == hashlib.sha256(raw_key.encode()).hexdigest() - assert not str(result["user_api_key_hash"]).startswith("sk-") - - -def test_get_standard_logging_metadata_raw_sk_key_via_user_api_key_is_hashed(): - """A raw `sk-` value arriving under the `user_api_key` alias is also hashed.""" - import hashlib - - raw_key = "sk-abcdefghijklmnopqrstuvwxyz" - metadata = {"user_api_key_hash": raw_key, "user_api_key": raw_key} - result = StandardLoggingPayloadSetup.get_standard_logging_metadata(metadata) - all_fields_present(result) - assert result["user_api_key_hash"] == hashlib.sha256(raw_key.encode()).hexdigest() - - -def test_get_standard_logging_metadata_valid_hash_unchanged(): - """An already-hashed value (valid sha256) is preserved verbatim.""" - valid_hash = "a" * 64 - metadata = {"user_api_key_hash": valid_hash} - result = StandardLoggingPayloadSetup.get_standard_logging_metadata(metadata) - all_fields_present(result) - assert result["user_api_key_hash"] == valid_hash - - -def test_get_standard_logging_metadata_non_sk_hash_value_unchanged(): - """ - Non-`sk-` opaque values in user_api_key_hash are left untouched (we scope the - rewrite narrowly to the raw-virtual-key shape to avoid mangling other ids). - """ - metadata = {"user_api_key_hash": "test_hash"} - result = StandardLoggingPayloadSetup.get_standard_logging_metadata(metadata) - all_fields_present(result) - assert result["user_api_key_hash"] == "test_hash" - - -def test_get_standard_logging_metadata_bearer_prefixed_raw_key_is_hashed(): - """ - SECURITY: a raw key still wrapped in its `Bearer ` Authorization prefix must - also be hashed -- `Bearer sk-...` must not slip through as cleartext. The - stored hash must equal the canonical hash of the *bare* key so attribution - joins still work. - """ - import hashlib - - raw_key = "sk-1234567890abcdefghijklmnop" - metadata = {"user_api_key_hash": f"Bearer {raw_key}"} - result = StandardLoggingPayloadSetup.get_standard_logging_metadata(metadata) - all_fields_present(result) - assert "sk-" not in str(result["user_api_key_hash"]) - assert result["user_api_key_hash"] == hashlib.sha256(raw_key.encode()).hexdigest() - - -def test_get_standard_logging_metadata_whitespace_padded_raw_key_is_hashed(): - """ - SECURITY: leading/trailing whitespace must not defeat the sanitizer. A - padded `" sk-..."` (or `" Bearer sk-... "`) must still be hashed, not - returned verbatim. - """ - import hashlib - - raw_key = "sk-1234567890abcdefghijklmnop" - for padded in (f" {raw_key}", f" Bearer {raw_key} "): - metadata = {"user_api_key_hash": padded} - result = StandardLoggingPayloadSetup.get_standard_logging_metadata(metadata) - all_fields_present(result) - assert "sk-" not in str(result["user_api_key_hash"]) - assert result["user_api_key_hash"] == hashlib.sha256(raw_key.encode()).hexdigest() - - -def test_get_standard_logging_metadata_raw_jwt_is_hashed(): - """ - SECURITY: a raw JWT placed in user_api_key_hash must not be persisted in - cleartext either. Delegating to the canonical sanitizer covers the JWT shape - (hashed as `hashed-jwt-`), not just `sk-` keys. - """ - raw_jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyLTEifQ.c2lnbmF0dXJl" - metadata = {"user_api_key_hash": raw_jwt} - result = StandardLoggingPayloadSetup.get_standard_logging_metadata(metadata) - all_fields_present(result) - assert result["user_api_key_hash"] != raw_jwt - assert str(result["user_api_key_hash"]).startswith("hashed-jwt-") - - -def test_get_standard_logging_metadata_absent_hash_stays_none(): - """No user_api_key_hash in input leaves the field as None (nothing to hash).""" - result = StandardLoggingPayloadSetup.get_standard_logging_metadata({}) - all_fields_present(result) - assert result["user_api_key_hash"] is None - - -def test_get_standard_logging_metadata_non_string_hash_unchanged(): - """A non-string value in user_api_key_hash is passed through unchanged.""" - metadata = {"user_api_key_hash": 12345} - result = StandardLoggingPayloadSetup.get_standard_logging_metadata(metadata) - all_fields_present(result) - assert result["user_api_key_hash"] == 12345 diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index edc257f4c3f..afbc1542f20 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -4101,3 +4101,131 @@ def test_pre_call_does_not_pin_request_in_module_state(logging_obj): logging_obj.post_call(original_response='{"ok": true}', input=big_input, api_key="sk-test") assert litellm.error_logs == {} + + +def test_get_standard_logging_metadata_raw_sk_key_in_hash_is_hashed(): + """ + SECURITY: a raw virtual key (`sk-...`) must never be persisted under + `user_api_key_hash`. It should be SHA-256 hashed before it can reach any + logging sink (e.g. the S3 request/llm logs). + """ + import hashlib + + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + raw_key = "sk-1234567890abcdefghijklmnop" + result = StandardLoggingPayloadSetup.get_standard_logging_metadata( + {"user_api_key_hash": raw_key} + ) + assert result["user_api_key_hash"] != raw_key + assert result["user_api_key_hash"] == hashlib.sha256(raw_key.encode()).hexdigest() + assert not str(result["user_api_key_hash"]).startswith("sk-") + + +def test_get_standard_logging_metadata_raw_sk_key_via_user_api_key_is_hashed(): + """A raw `sk-` value arriving under the `user_api_key` alias is also hashed.""" + import hashlib + + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + raw_key = "sk-abcdefghijklmnopqrstuvwxyz" + result = StandardLoggingPayloadSetup.get_standard_logging_metadata( + {"user_api_key_hash": raw_key, "user_api_key": raw_key} + ) + assert result["user_api_key_hash"] == hashlib.sha256(raw_key.encode()).hexdigest() + + +def test_get_standard_logging_metadata_valid_hash_unchanged(): + """An already-hashed value (valid sha256) is preserved verbatim.""" + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + valid_hash = "a" * 64 + result = StandardLoggingPayloadSetup.get_standard_logging_metadata( + {"user_api_key_hash": valid_hash} + ) + assert result["user_api_key_hash"] == valid_hash + + +def test_get_standard_logging_metadata_non_sk_hash_value_unchanged(): + """ + Non-`sk-` opaque values in user_api_key_hash are left untouched (we scope the + rewrite narrowly to the raw-virtual-key shape to avoid mangling other ids). + """ + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + result = StandardLoggingPayloadSetup.get_standard_logging_metadata( + {"user_api_key_hash": "test_hash"} + ) + assert result["user_api_key_hash"] == "test_hash" + + +def test_get_standard_logging_metadata_bearer_prefixed_raw_key_is_hashed(): + """ + SECURITY: a raw key still wrapped in its `Bearer ` Authorization prefix must + also be hashed -- `Bearer sk-...` must not slip through as cleartext. The + stored hash must equal the canonical hash of the *bare* key so attribution + joins still work. + """ + import hashlib + + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + raw_key = "sk-1234567890abcdefghijklmnop" + result = StandardLoggingPayloadSetup.get_standard_logging_metadata( + {"user_api_key_hash": f"Bearer {raw_key}"} + ) + assert "sk-" not in str(result["user_api_key_hash"]) + assert result["user_api_key_hash"] == hashlib.sha256(raw_key.encode()).hexdigest() + + +def test_get_standard_logging_metadata_whitespace_padded_raw_key_is_hashed(): + """ + SECURITY: leading/trailing whitespace must not defeat the sanitizer. A + padded `" sk-..."` (or `" Bearer sk-... "`) must still be hashed, not + returned verbatim. + """ + import hashlib + + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + raw_key = "sk-1234567890abcdefghijklmnop" + for padded in (f" {raw_key}", f" Bearer {raw_key} "): + result = StandardLoggingPayloadSetup.get_standard_logging_metadata( + {"user_api_key_hash": padded} + ) + assert "sk-" not in str(result["user_api_key_hash"]) + assert result["user_api_key_hash"] == hashlib.sha256(raw_key.encode()).hexdigest() + + +def test_get_standard_logging_metadata_raw_jwt_is_hashed(): + """ + SECURITY: a raw JWT placed in user_api_key_hash must not be persisted in + cleartext either. The sanitizer covers the JWT shape (hashed as + `hashed-jwt-`), not just `sk-` keys. + """ + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + raw_jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyLTEifQ.c2lnbmF0dXJl" + result = StandardLoggingPayloadSetup.get_standard_logging_metadata( + {"user_api_key_hash": raw_jwt} + ) + assert result["user_api_key_hash"] != raw_jwt + assert str(result["user_api_key_hash"]).startswith("hashed-jwt-") + + +def test_get_standard_logging_metadata_absent_hash_stays_none(): + """No user_api_key_hash in input leaves the field as None (nothing to hash).""" + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + result = StandardLoggingPayloadSetup.get_standard_logging_metadata({}) + assert result["user_api_key_hash"] is None + + +def test_get_standard_logging_metadata_non_string_hash_unchanged(): + """A non-string value in user_api_key_hash is passed through unchanged.""" + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + result = StandardLoggingPayloadSetup.get_standard_logging_metadata( + {"user_api_key_hash": 12345} + ) + assert result["user_api_key_hash"] == 12345