Skip to content

Commit b2096b9

Browse files
v1.1.8 🔱
1 parent d79f99e commit b2096b9

File tree

5 files changed

+35
-5
lines changed

5 files changed

+35
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.1.8] - 2025-10-03 :notes:
9+
10+
- Improve the `Secret` class to always encode strings before comparing to `str`.
11+
812
## [1.1.7] - 2025-10-01 :fallen_leaf:
913

1014
- Add a `Secret` class to handle secrets in code instead of using plain `str`. This

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 Roberto Prevato
3+
Copyright (c) 2019-present Roberto Prevato, [email protected]
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

essentials/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.1.7"
1+
__version__ = "1.1.8"

essentials/secrets.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,18 @@ class Secret:
2424
"Secret('******')"
2525
"""
2626

27-
def __init__(self, value: str, *, direct_value: bool = False) -> None:
27+
def __init__(
28+
self, value: str, *, direct_value: bool = False, encoding: str = "utf8"
29+
) -> None:
2830
"""
2931
Create an instance of Secret.
3032
3133
Args:
3234
value: The name of an environment variable reference (prefixed with $), or
3335
a secret if direct_value=True.
3436
direct_value: Must be set to True to allow passing secrets directly
37+
encoding: The encoding to be used when comparing strings with
38+
secrets.compare_digest.
3539
3640
Raises:
3741
ValueError: If a secret is provided without explicit permission.
@@ -45,6 +49,7 @@ def __init__(self, value: str, *, direct_value: bool = False) -> None:
4549
"directly."
4650
)
4751
self._value = value
52+
self._encoding = encoding
4853
# Validate that we can retrieve a value
4954
value = self.get_value()
5055
if not isinstance(value, str) or not value:
@@ -86,6 +91,9 @@ def get_value(self) -> str:
8691
return value
8792
return self._value
8893

94+
def _get_value_bytes(self) -> bytes:
95+
return self.get_value().encode(self._encoding)
96+
8997
def __str__(self) -> str:
9098
return "******" # Never expose the actual value
9199

@@ -102,9 +110,11 @@ def __eq__(self, other: object) -> bool:
102110
if isinstance(other, str):
103111
# Using constant-time comparison to prevent timing attacks, with
104112
# secrets.compare_digest.
105-
return secrets.compare_digest(self.get_value(), other)
113+
return secrets.compare_digest(
114+
self._get_value_bytes(), other.encode(self._encoding, errors="ignore")
115+
)
106116

107117
if not isinstance(other, Secret):
108118
return NotImplemented
109119

110-
return secrets.compare_digest(self.get_value(), other.get_value())
120+
return secrets.compare_digest(self._get_value_bytes(), other._get_value_bytes())

tests/test_secrets.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,19 @@ def test_from_plain_text_with_empty_string():
138138
"""Test that from_plain_text with empty string raises ValueError."""
139139
with pytest.raises(ValueError, match="Secret value must be a non-empty string"):
140140
Secret.from_plain_text("")
141+
142+
143+
def test_secrets_support_utf8_chars():
144+
secret = Secret("ØØ Void", direct_value=True)
145+
assert secret == "ØØ Void"
146+
147+
148+
def test_secrets_work_with_invalid_unicode():
149+
secret = Secret("ØØ Void", direct_value=True)
150+
assert secret != "Hello\ud800World" # invalid unicode
151+
152+
153+
def test_secrets_with_special_characters():
154+
"""Test secrets with various special Unicode characters."""
155+
secret = Secret("🔐密码🌟", direct_value=True)
156+
assert secret == "🔐密码🌟"

0 commit comments

Comments
 (0)