Skip to content

Commit 8d12b0e

Browse files
committed
fix(tests): use context-aware regex for ds_password normalization
Replace naive string substitution for ds_password with a regex that only matches 'password: <value>' in YAML and '"password": "<value>"' in JSON contexts. The previous plain-text replacement of 'secret' corrupted values containing it as a substring (e.g. 'client-secret' in data source IDs). risk: low
1 parent d0ac128 commit 8d12b0e

1 file changed

Lines changed: 13 additions & 2 deletions

File tree

packages/tests-support/src/tests_support/vcrpy_utils.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
# Each entry is (source_string, replacement_string).
5757
# Ordered longest-first so more specific patterns match before substrings.
5858
_normalization_replacements: list[tuple[str, str]] = []
59+
_password_replacements: list[tuple[re.Pattern, str]] = []
5960
_normalization_configured: bool = False
6061

6162
# --- Timestamp normalization ---
@@ -121,8 +122,9 @@ def configure_normalization(test_config: dict[str, Any]) -> None:
121122
rm -rf packages/gooddata-sdk/.tox
122123
uv cache clean tests-support --force
123124
"""
124-
global _normalization_replacements, _normalization_configured
125+
global _normalization_replacements, _password_replacements, _normalization_configured
125126
replacements: list[tuple[str, str]] = []
127+
_password_replacements = []
126128

127129
parsed = urlparse(test_config.get("host", _CANONICAL_HOST))
128130
active_scheme = parsed.scheme or "http"
@@ -177,9 +179,16 @@ def configure_normalization(test_config: dict[str, Any]) -> None:
177179
replacements.append((active_ds_url, _CANONICAL_DS_URL))
178180

179181
# Data source password: staging password → local password
182+
# Uses regex (not plain string replacement) to avoid corrupting values
183+
# that contain the password as a substring (e.g. "client-secret").
180184
active_ds_password = test_config.get("ds_password", _CANONICAL_DS_PASSWORD)
181185
if active_ds_password and active_ds_password != _CANONICAL_DS_PASSWORD:
182-
replacements.append((active_ds_password, _CANONICAL_DS_PASSWORD))
186+
_ds_password_re = re.compile(
187+
r'(?<="password": ")' + re.escape(active_ds_password) + r'(?=")' # JSON
188+
r"|"
189+
r"(?<=password: )" + re.escape(active_ds_password) + r"(?=\s|$)" # YAML
190+
)
191+
_password_replacements.append((_ds_password_re, _CANONICAL_DS_PASSWORD))
183192

184193
# Sort by length descending so longer patterns are replaced first,
185194
# preventing partial matches from corrupting longer strings.
@@ -193,6 +202,8 @@ def _apply_replacements(text: str) -> str:
193202
"""Apply all configured normalization replacements to a string."""
194203
for source, target in _normalization_replacements:
195204
text = text.replace(source, target)
205+
for pattern, target in _password_replacements:
206+
text = pattern.sub(target, text)
196207
return text
197208

198209

0 commit comments

Comments
 (0)