From bdc924b251383bc1b35faaa8f4609bb67f691301 Mon Sep 17 00:00:00 2001 From: Kolade Fajimi <107228310+koladefaj@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:38:26 +0100 Subject: [PATCH] fix(vertex): redact the credentials path before it reaches the log --- litellm/llms/vertex_ai/credentials_source.py | 8 +++++--- .../llms/vertex_ai/test_vertex_llm_base.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/litellm/llms/vertex_ai/credentials_source.py b/litellm/llms/vertex_ai/credentials_source.py index 31510a8981e..6c86d02ad21 100644 --- a/litellm/llms/vertex_ai/credentials_source.py +++ b/litellm/llms/vertex_ai/credentials_source.py @@ -15,6 +15,7 @@ from pydantic import TypeAdapter, ValidationError from typing_extensions import assert_never from litellm._logging import verbose_logger +from litellm.litellm_core_utils.secret_redaction import redact_string @dataclass(frozen=True, slots=True) @@ -120,17 +121,18 @@ def raise_vertex_credentials_failure(failure: VertexCredentialsFailure) -> NoRet The caller is told which of the three faults it was; the path goes to the proxy log instead, because the message reaches whoever sent the request and the operator who can - act on the path is reading the log anyway. + act on the path is reading the log anyway. The path is redacted on the way there too, so + a credential misconfigured into this field does not become a log entry. """ match failure: case VertexCredentialsFileUnreadable(path=path, reason=reason): - verbose_logger.error("Vertex: cannot read the credentials file at %s: %s", path, reason) + verbose_logger.error("Vertex: cannot read the credentials file at %s: %s", redact_string(path), reason) raise ValueError( f"Unable to read the vertex credentials file: {reason}. The proxy log names the path. " "Set `vertex_credentials` to a readable file path, or to the credentials JSON itself." ) case VertexCredentialsFileNotJson(path=path, detail=detail): - verbose_logger.error("Vertex: credentials file at %s is not valid JSON: %s", path, detail) + verbose_logger.error("Vertex: credentials file at %s is not valid JSON: %s", redact_string(path), detail) raise ValueError( f"The vertex credentials file is not valid JSON: {detail}. The proxy log names the path. " "Check for unescaped newlines in private_key." diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex_llm_base.py b/tests/test_litellm/llms/vertex_ai/test_vertex_llm_base.py index 6ef89fd8163..e636b8c2637 100644 --- a/tests/test_litellm/llms/vertex_ai/test_vertex_llm_base.py +++ b/tests/test_litellm/llms/vertex_ai/test_vertex_llm_base.py @@ -2304,6 +2304,17 @@ class TestVertexCredentialsSource: assert project_id == "from-inline" assert from_sa.call_args.args[0] == {"type": "service_account", "project_id": "from-inline"} + def test_a_credential_misconfigured_as_a_path_is_not_logged_either(self, caplog): + """The path reaches the log, so a credential put in that field must be scrubbed first.""" + pem = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADA\n-----END PRIVATE KEY-----" + + with caplog.at_level(logging.ERROR, logger="LiteLLM"): + with pytest.raises(ValueError, match="Unable to read the vertex credentials file"): + VertexBase().load_auth(credentials=pem, project_id="p") + + assert "MIIEvQIBADA" not in caplog.text + assert "REDACTED" in caplog.text + def test_a_path_open_rejects_outright_is_reported_not_raised_raw(self): """open() rejects some paths with a bare ValueError before any filesystem call.""" with pytest.raises(ValueError, match="Unable to read the vertex credentials file") as exc_info: