Beispiel
pydantic 2.13.4: Preserve SecretStr and SecretBytes plain values during Python model_dump while preventing secret value corruption across JSON serialization and deserialization cycles in Pydantic 2.13.4
Verifiziertes Beispiel für pypi pydantic 2.13.4: Preserve SecretStr and SecretBytes plain values during Python model_dump while preventing secret value…
sha256:e5a1e6b5f189f5c49af04e8a71b357635addc81edb18abeb301d700e63678557
Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen.
Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen.
MIT-0
Ausführungsbelege
Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.
- Beleggrundlage
- Signierter Vertrag bestanden
- Verifizierungsbelege
- 2
- Signaturschlüssel, die es gebaut haben
- 2
Deklarierte Umgebung
python linux x64 python python pip
Umgebungen der Verifizierungsläufe
| Umgebung | Contract | Stufen | Lauf |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-17 |
| python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-18 |
Fall
HOW- Ziel
- Preserve SecretStr and SecretBytes plain values during Python model_dump while preventing secret value corruption across JSON serialization and deserialization cycles in Pydantic 2.13.4
- Pakete
- Symbole
-
- pydantic.types.SecretStr
- pydantic.types.SecretBytes
- pydantic.BaseModel.model_dump
- pydantic.BaseModel.model_dump_json
- pydantic.BaseModel.model_validate
- pydantic.BaseModel.model_validate_json
- Umgebung
- python
- Erstellt
- 2026-08-17T06:49:32Z
Contract
- model_dump_json masks SecretStr and SecretBytes to asterisks, and subsequent model_validate_json silently parses the mask string as the new secret value instead of preserving the secret or raising an error.
- model_dump() in default python mode preserves SecretStr and SecretBytes objects, enabling lossless model_validate() roundtripping and explicit get_secret_value() extraction.
- model_dump(mode='json') outputs masked strings identical to model_dump_json, shedding SecretStr and SecretBytes types.
- SecretStr and SecretBytes mask their payload in str() and repr(), but len() reflects the underlying secret length rather than mask length.
- SecretStr never compares equal to a plain str or permits concatenation, preventing accidental plaintext leak via equality or string operations.
Dateien
- NOTES.md
- csx.json
- requirements.lock
- requirements.txt
- src/__init__.py
- src/models.py
- test/contract.py
Quelltext
# Pydantic SecretStr and SecretBytes serialization boundaries
`search_known_solution` returned existing contracts for model copy trust boundaries (`model_copy(update=...)`, sample `sha256:a7f8bfd32813f975440d7e5007452b0320c4fef7237de0ea68ad49b75a676d7f`), query parameter binding, URL type preservation, and regex error structures. None proved `SecretStr` and `SecretBytes` serialization masking, length introspection, or round-trip validation behavior.
Pinned release: `pydantic==2.13.4` (with `pydantic-core==2.46.4`).
## Observed Behaviour and Silent Corruption Trap
When a Pydantic model contains sensitive fields typed as `SecretStr` or `SecretBytes`, developers often expect JSON serialization and deserialization to either safely round-trip the credential or fail loudly if plain secrets cannot be serialized.
In Pydantic 2.13.4:
1. `model_dump_json()` and `model_dump(mode='json')` mask sensitive fields to fixed asterisk strings (`'**********'`).
2. When deserializing that JSON payload with `model_validate_json()`, Pydantic does not reject the asterisk string or warn that the value was masked. Instead, validation succeeds silently with a green build, instantiating a `SecretStr` or `SecretBytes` where the underlying secret value is literally `"**********"` or `b"**********"`.
3. The original secret is destroyed, and subsequent operations (such as authentication headers, token signatures, or database logins) fail downstream at runtime with invalid credentials.
## Safe Handling
- For in-memory model replication or caching within Python processes, use `model_dump()` in default Python mode (`mode='python'`), which retains `SecretStr` and `SecretBytes` instances without exposing or replacing their raw contents. Rebuilding via `model_validate(dumped_dict)` preserves the unmasked secret value losslessly.
- To access the unmasked plain value when communicating with external APIs or cryptographic primitives, explicitly call `.get_secret_value()`.
- `len(secret)` reports the true underlying length of the secret payload, whereas `str(secret)` and `repr(secret)` return the masked representation.
- `SecretStr` instances never evaluate as equal to plain strings (`secret != 'raw_string'`) and reject string concatenation operators (`+`), preventing accidental leakages via type coercion.
{"case":{"believed":"Serializing a Pydantic model to JSON and parsing it back with model_validate_json preserves the original plaintext secret or raises a validation error.","caseId":"case:sha256:a7a72202ba4be9dd5fdea6fc905c955a8568564e50d45ca11a0bf6785cb658cf","contract":["model_dump_json masks SecretStr and SecretBytes to asterisks, and subsequent model_validate_json silently parses the mask string as the new secret value instead of preserving the secret or raising an error.","model_dump() in default python mode preserves SecretStr and SecretBytes objects, enabling lossless model_validate() roundtripping and explicit get_secret_value() extraction.","model_dump(mode='json') outputs masked strings identical to model_dump_json, shedding SecretStr and SecretBytes types.","SecretStr and SecretBytes mask their payload in str() and repr(), but len() reflects the underlying secret length rather than mask length.","SecretStr never compares equal to a plain str or permits concatenation, preventing accidental plaintext leak via equality or string operations."],"goal":"Preserve SecretStr and SecretBytes plain values during Python model_dump while preventing secret value corruption across JSON serialization and deserialization cycles in Pydantic 2.13.4","kind":"HOW","packages":["pkg:pypi/pydantic@2.13.4"],"schemaVersion":1,"symbols":["pydantic.types.SecretStr","pydantic.types.SecretBytes","pydantic.BaseModel.model_dump","pydantic.BaseModel.model_dump_json","pydantic.BaseModel.model_validate","pydantic.BaseModel.model_validate_json"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/pydantic@2.13.4"],"schemaVersion":1,"symbols":["pydantic.types.SecretStr","pydantic.types.SecretBytes","pydantic.BaseModel.model_dump","pydantic.BaseModel.model_dump_json","pydantic.BaseModel.model_validate","pydantic.BaseModel.model_validate_json"],"verifierAdapter":"python@1"}
annotated-types==0.8.0
pydantic==2.13.4
pydantic-core==2.46.4
typing-extensions==4.15.0
typing-inspection==0.4.2
annotated-types==0.8.0
pydantic==2.13.4
pydantic-core==2.46.4
typing-extensions==4.15.0
typing-inspection==0.4.2
"""Source package initialization."""
from pydantic import BaseModel, ConfigDict, SecretBytes, SecretStr
class ServiceCredentials(BaseModel):
model_config = ConfigDict(extra="forbid")
service_name: str
api_token: SecretStr
hmac_key: SecretBytes
import json
import sys
from importlib.metadata import version
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from pydantic import SecretBytes, SecretStr
from src.models import ServiceCredentials
def test_json_roundtrip_silent_corruption() -> None:
original = ServiceCredentials(
service_name="auth-service",
api_token=SecretStr("super-secret-token-xyz"),
hmac_key=SecretBytes(b"raw-binary-key-12345"),
)
json_str = original.model_dump_json()
data = json.loads(json_str)
assert data["api_token"] == "**********"
assert data["hmac_key"] == "**********"
restored = ServiceCredentials.model_validate_json(json_str)
# The secret value is silently replaced by the mask string
assert restored.api_token.get_secret_value() == "**********"
assert restored.hmac_key.get_secret_value() == b"**********"
assert restored.api_token.get_secret_value() != "super-secret-token-xyz"
assert restored.hmac_key.get_secret_value() != b"raw-binary-key-12345"
def test_python_dump_lossless_roundtrip() -> None:
original = ServiceCredentials(
service_name="auth-service",
api_token=SecretStr("super-secret-token-xyz"),
hmac_key=SecretBytes(b"raw-binary-key-12345"),
)
py_dict = original.model_dump()
assert isinstance(py_dict["api_token"], SecretStr)
assert isinstance(py_dict["hmac_key"], SecretBytes)
assert py_dict["api_token"].get_secret_value() == "super-secret-token-xyz"
assert py_dict["hmac_key"].get_secret_value() == b"raw-binary-key-12345"
restored = ServiceCredentials.model_validate(py_dict)
assert restored.api_token.get_secret_value() == "super-secret-token-xyz"
assert restored.hmac_key.get_secret_value() == b"raw-binary-key-12345"
def test_json_mode_dump() -> None:
original = ServiceCredentials(
service_name="auth-service",
api_token=SecretStr("super-secret-token-xyz"),
hmac_key=SecretBytes(b"raw-binary-key-12345"),
)
json_mode_dict = original.model_dump(mode="json")
assert json_mode_dict["api_token"] == "**********"
assert isinstance(json_mode_dict["api_token"], str)
assert json_mode_dict["hmac_key"] == "**********"
assert isinstance(json_mode_dict["hmac_key"], str)
def test_masking_and_length_semantics() -> None:
token = SecretStr("short")
key = SecretBytes(b"a_much_longer_raw_key_payload_bytes_here")
# str() and repr() mask content
assert str(token) == "**********"
assert repr(token) == "SecretStr('**********')"
assert str(key) == "b'**********'"
assert repr(key) == "SecretBytes(b'**********')"
# len() returns real payload size, while len(str()) is fixed mask formatting length
assert len(str(token)) == 10
assert len(token) == 5
assert len(str(key)) == 13
assert len(key) == 40
def test_equality_and_type_isolation() -> None:
token = SecretStr("my-password")
raw_str = "my-password"
# SecretStr does not compare equal to raw string
assert token != raw_str
assert token == SecretStr("my-password")
assert token != SecretStr("other-password")
# SecretStr prevents string concatenation
try:
_ = token + "-suffix"
except TypeError:
pass
else:
raise AssertionError("SecretStr must reject concatenation with str")
# Explicit get_secret_value() is required to extract the plaintext
assert token.get_secret_value() == raw_str
if __name__ == "__main__":
assert version("pydantic") == "2.13.4"
test_json_roundtrip_silent_corruption()
test_python_dump_lossless_roundtrip()
test_json_mode_dump()
test_masking_and_length_semantics()
test_equality_and_type_isolation()
print("CONTRACT PASS: Pydantic SecretStr and SecretBytes semantics verified")