From 269ee7e876848567c3bfcb083e3bd85949ede9d0 Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Thu, 9 Jul 2026 20:32:30 +0300 Subject: [PATCH] fix(proxy): never log billing credential values when they fail to resolve Accepting inline PEM turned the cert env vars into secret-bearing values, but the disable warning still echoed them. A value that is neither a readable path nor `-----BEGIN`-prefixed PEM, for example a key with a preamble or a malformed secret, fell through to the path branch and was written to the proxy logs verbatim, exposing the client certificate or private key to anyone who can read them. The warning now names the offending environment variables and tells the operator what a valid value looks like, without ever printing one --- .../enterprise_billing/billing_metrics.py | 20 +++++++++++---- .../test_billing_metrics.py | 25 +++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/enterprise_billing/billing_metrics.py b/litellm/proxy/enterprise_billing/billing_metrics.py index e66a337f7a0..89aac8b38e7 100644 --- a/litellm/proxy/enterprise_billing/billing_metrics.py +++ b/litellm/proxy/enterprise_billing/billing_metrics.py @@ -229,14 +229,24 @@ def load_billing_metrics_config( ) return None - required_paths = [paths.client_cert_path, paths.client_key_path] + ( - [paths.ca_cert_path] if paths.ca_cert_path else [] - ) - unreadable = [path for path in required_paths if not os.path.isfile(path)] + # Report the variable names, never their values. A value that is neither a + # readable path nor recognizable PEM is still secret material, and this + # warning would otherwise copy a client key straight into the proxy logs. + unreadable = [ + env_name + for env_name, path in ( + (CLIENT_CERT_ENV, paths.client_cert_path), + (CLIENT_KEY_ENV, paths.client_key_path), + (CA_CERT_ENV, paths.ca_cert_path), + ) + if path and not os.path.isfile(path) + ] if unreadable: verbose_proxy_logger.warning( - "Enterprise billing metrics disabled: certificate file(s) not found: %s", + "Enterprise billing metrics disabled: %s did not resolve to a readable certificate file. " + "Set each to a file path, or to inline PEM content beginning with '%s'.", ", ".join(unreadable), + _PEM_PREFIX, ) return None diff --git a/tests/test_litellm/proxy/enterprise_billing/test_billing_metrics.py b/tests/test_litellm/proxy/enterprise_billing/test_billing_metrics.py index 3666440ae16..d7a564c6b7c 100644 --- a/tests/test_litellm/proxy/enterprise_billing/test_billing_metrics.py +++ b/tests/test_litellm/proxy/enterprise_billing/test_billing_metrics.py @@ -240,6 +240,31 @@ def test_load_config_with_inline_pem_disabled_when_unwritable(monkeypatch): assert bm.load_billing_metrics_config(license_data=None, litellm_version="1.0") is None +def test_load_config_never_logs_credential_values(monkeypatch): + """ + A value that is neither a readable path nor `-----BEGIN`-prefixed PEM is + still secret material. The disable warning must name the env vars, never + echo their contents, or a malformed key lands in the proxy logs. + """ + secret_material = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ-not-pem-prefixed" + monkeypatch.setenv(bm.ENDPOINT_ENV, "https://collector.example:4317") + monkeypatch.setenv(bm.CLIENT_CERT_ENV, secret_material) + monkeypatch.setenv(bm.CLIENT_KEY_ENV, secret_material) + + logged: List[str] = [] + + def _capture(msg, *args): + logged.append(msg % args if args else msg) + + monkeypatch.setattr(bm.verbose_proxy_logger, "warning", _capture) + + assert bm.load_billing_metrics_config(license_data=None, litellm_version="1.0") is None + + joined = "\n".join(logged) + assert secret_material not in joined + assert bm.CLIENT_CERT_ENV in joined and bm.CLIENT_KEY_ENV in joined + + def test_load_config_with_empty_pem_env_is_disabled(monkeypatch): """Empty stays empty: an unset secret must not be mistaken for inline PEM.""" monkeypatch.setenv(bm.ENDPOINT_ENV, "https://collector.example:4317")