From 5ad676f92a639b447f77e551b0b2f73a0e8f666a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 16 Sep 2026 13:32:51 +0000 Subject: [PATCH 1/4] test(serializer): cover pydantic secret masking Co-authored-by: Hassieb Pakzad --- tests/unit/test_serializer.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_serializer.py b/tests/unit/test_serializer.py index ce5798f67..02e87fabc 100644 --- a/tests/unit/test_serializer.py +++ b/tests/unit/test_serializer.py @@ -7,7 +7,7 @@ from uuid import UUID import pytest -from pydantic import BaseModel +from pydantic import BaseModel, SecretBytes, SecretStr from langfuse._utils.serializer import ( EventSerializer, @@ -29,6 +29,11 @@ class TestBaseModel(BaseModel): field: str +class TestSecretBaseModel(BaseModel): + api_key: SecretStr + token: SecretBytes + + def test_datetime(): dt = datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc) serializer = EventSerializer() @@ -71,6 +76,32 @@ def test_pydantic_model(): assert json.loads(serializer.encode(model)) == {"field": "test"} +@pytest.mark.parametrize( + "secret", + [ + SecretStr("not-a-real-api-key"), + SecretBytes(b"not-a-real-token"), + ], +) +def test_pydantic_secret(secret): + serializer = EventSerializer() + + assert serializer.encode(secret) == json.dumps(str(secret)) + + +def test_pydantic_model_with_secrets(): + model = TestSecretBaseModel( + api_key=SecretStr("not-a-real-api-key"), + token=SecretBytes(b"not-a-real-token"), + ) + serializer = EventSerializer() + + assert json.loads(serializer.encode(model)) == { + "api_key": "**********", + "token": "b'**********'", + } + + def test_langfuse_media_reference_serializes_to_reference_string(): # Resolved references must round-trip back to their original reference string # rather than falling through to asdict() and emitting an opaque dict. From a30c92dca5f000adec4cf7cb2e9dbfdfd493ea0e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 16 Sep 2026 13:33:22 +0000 Subject: [PATCH 2/4] fix(serializer): mask pydantic secret values Co-authored-by: Hassieb Pakzad --- langfuse/_utils/serializer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/langfuse/_utils/serializer.py b/langfuse/_utils/serializer.py index 46188fce5..9000acd3e 100644 --- a/langfuse/_utils/serializer.py +++ b/langfuse/_utils/serializer.py @@ -13,7 +13,7 @@ from typing import Any from uuid import UUID -from pydantic import BaseModel +from pydantic import BaseModel, SecretBytes, SecretStr from langfuse.media import LangfuseMedia, LangfuseMediaReference @@ -52,6 +52,9 @@ def default(self, obj: Any) -> Any: def _default_inner(self, obj: Any) -> Any: try: + if isinstance(obj, (SecretStr, SecretBytes)): + return str(obj) + if isinstance(obj, (datetime)): # Timezone-awareness check return serialize_datetime(obj) From eaa4d79f3aba16ba2f6623c85e74f24896d5cad7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 16 Sep 2026 13:34:30 +0000 Subject: [PATCH 3/4] test(serializer): avoid test model collection warning Co-authored-by: Hassieb Pakzad --- tests/unit/test_serializer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_serializer.py b/tests/unit/test_serializer.py index 02e87fabc..7106b52fd 100644 --- a/tests/unit/test_serializer.py +++ b/tests/unit/test_serializer.py @@ -29,7 +29,7 @@ class TestBaseModel(BaseModel): field: str -class TestSecretBaseModel(BaseModel): +class SecretBaseModel(BaseModel): api_key: SecretStr token: SecretBytes @@ -90,7 +90,7 @@ def test_pydantic_secret(secret): def test_pydantic_model_with_secrets(): - model = TestSecretBaseModel( + model = SecretBaseModel( api_key=SecretStr("not-a-real-api-key"), token=SecretBytes(b"not-a-real-token"), ) From e8cb4828ab05b0bc1bd12501a80403ae4cf30639 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 16 Sep 2026 15:39:20 +0000 Subject: [PATCH 4/4] fix(serializer): use stable secret placeholder Co-authored-by: Hassieb Pakzad --- langfuse/_utils/serializer.py | 2 +- tests/unit/test_serializer.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/langfuse/_utils/serializer.py b/langfuse/_utils/serializer.py index 9000acd3e..ac3063237 100644 --- a/langfuse/_utils/serializer.py +++ b/langfuse/_utils/serializer.py @@ -53,7 +53,7 @@ def default(self, obj: Any) -> Any: def _default_inner(self, obj: Any) -> Any: try: if isinstance(obj, (SecretStr, SecretBytes)): - return str(obj) + return "" if isinstance(obj, (datetime)): # Timezone-awareness check diff --git a/tests/unit/test_serializer.py b/tests/unit/test_serializer.py index 7106b52fd..48e6fc292 100644 --- a/tests/unit/test_serializer.py +++ b/tests/unit/test_serializer.py @@ -86,7 +86,7 @@ def test_pydantic_model(): def test_pydantic_secret(secret): serializer = EventSerializer() - assert serializer.encode(secret) == json.dumps(str(secret)) + assert serializer.encode(secret) == '""' def test_pydantic_model_with_secrets(): @@ -97,8 +97,8 @@ def test_pydantic_model_with_secrets(): serializer = EventSerializer() assert json.loads(serializer.encode(model)) == { - "api_key": "**********", - "token": "b'**********'", + "api_key": "", + "token": "", }