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")