From 1eb65b3b6c06c9ac7a6f27bc018f7ec4bbdf9b65 Mon Sep 17 00:00:00 2001 From: Nimish Date: Sat, 27 Dec 2025 12:59:12 +0530 Subject: [PATCH 1/4] fix: all include (?!\{) to ignore Railway-style ${{...}} syntax --- phase_cli/utils/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phase_cli/utils/const.py b/phase_cli/utils/const.py index 88e18d8d..628b20e8 100644 --- a/phase_cli/utils/const.py +++ b/phase_cli/utils/const.py @@ -21,7 +21,7 @@ |__/ """ -SECRET_REF_REGEX = re.compile(r"\$\{([^}]+)\}") +SECRET_REF_REGEX = re.compile(r"\$\{(?!\{)([^}]+)\}") # Define paths to Phase configs PHASE_ENV_CONFIG = ".phase.json" # Holds project and environment contexts in users repo, unique to each application. From 5b2de7f7d33211c3ac09aab0f3633b9e8e155cd6 Mon Sep 17 00:00:00 2001 From: Nimish Date: Sat, 27 Dec 2025 13:03:29 +0530 Subject: [PATCH 2/4] fix: update regex patterns for cross-environment and local references - Modified regex patterns to improve handling of cross-application environment variables and local references, ensuring compatibility with various syntax formats. - Introduced checks to ignore Railway-style syntax, enhancing flexibility in environment variable parsing. --- phase_cli/utils/const.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phase_cli/utils/const.py b/phase_cli/utils/const.py index 628b20e8..e31487cb 100644 --- a/phase_cli/utils/const.py +++ b/phase_cli/utils/const.py @@ -47,5 +47,6 @@ r"^pss_service:v(\d+):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64})$" ) -cross_env_pattern = re.compile(r"\$\{(.+?)\.(.+?)\}") -local_ref_pattern = re.compile(r"\$\{([^.]+?)\}") +CROSS_APP_ENV_PATTERN = re.compile(r"\$\{(?!\{)(.+?)::(.+?)\.(.+?)\}") +CROSS_ENV_PATTERN = re.compile(r"\$\{(?!\{)(?![^{]*::)([^.]+?)\.(.+?)\}") +LOCAL_REF_PATTERN = re.compile(r"\$\{(?!\{)([^.]+?)\}") From e8656284014f226b6cdc1d2a70bffed9bbc49c39 Mon Sep 17 00:00:00 2001 From: Nimish Date: Sat, 27 Dec 2025 13:03:50 +0530 Subject: [PATCH 3/4] refactor: update regex pattern references in misc.py - Replaced local variable references for cross-environment and local reference patterns with constants for improved clarity and maintainability. - This change enhances the readability of the code and ensures consistent usage of the defined patterns throughout the module. --- phase_cli/utils/misc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phase_cli/utils/misc.py b/phase_cli/utils/misc.py index 00251ec7..dbbca0fd 100644 --- a/phase_cli/utils/misc.py +++ b/phase_cli/utils/misc.py @@ -12,7 +12,7 @@ from rich.box import ROUNDED from urllib.parse import urlparse from typing import Union, List -from phase_cli.utils.const import __version__, PHASE_ENV_CONFIG, PHASE_CLOUD_API_HOST, PHASE_SECRETS_DIR, cross_env_pattern, local_ref_pattern +from phase_cli.utils.const import __version__, PHASE_ENV_CONFIG, PHASE_CLOUD_API_HOST, PHASE_SECRETS_DIR, CROSS_ENV_PATTERN, LOCAL_REF_PATTERN import platform import shutil @@ -171,8 +171,8 @@ def format_secret_row(secret, value_width, show): comment = " 💬" if secret.get("comment") else "" key_display = f"{key}{tags}{comment}" - icon = '⛓️ ' if cross_env_pattern.search(value) else '' - icon += '🔗 ' if local_ref_pattern.search(value) else '' + icon = '⛓️ ' if CROSS_ENV_PATTERN.search(value) else '' + icon += '🔗 ' if LOCAL_REF_PATTERN.search(value) else '' personal_indicator = '🔏 ' if secret.get("overridden", False) else '' From 0f79f3fdd2c80281fd506bde0debfa2b5f1bcb77 Mon Sep 17 00:00:00 2001 From: Nimish Date: Sat, 27 Dec 2025 13:05:35 +0530 Subject: [PATCH 4/4] test: add syntax preservation tests for Railway and GitHub Actions - Introduced new tests to ensure that Railway-style ${{...}} and GitHub Actions ${{ secrets.X }} syntax are preserved during secret resolution. - Added tests for mixed references and secret values containing Railway syntax to validate correct behavior in various scenarios. - These enhancements improve the robustness of the secret resolution functionality by preventing unintended alterations to specific syntax formats. --- tests/secret_referencing.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/secret_referencing.py b/tests/secret_referencing.py index 735677be..703e79c6 100644 --- a/tests/secret_referencing.py +++ b/tests/secret_referencing.py @@ -291,3 +291,51 @@ def test_multiple_occurrences_same_reference(phase, current_application_name, cu ] resolved_value = resolve_all_secrets(value, all_secrets, phase, current_application_name, current_env_name) assert resolved_value == "A=v;B=v" + + +# ============================================================================= +# Syntax preservation tests to prevent referencing syntax overalp with third party platforms like Railway with ${{...}} +# ============================================================================= + +def test_railway_syntax_preserved(phase, current_application_name, current_env_name): + """Railway-style ${{...}} syntax should NOT be treated as a secret reference.""" + value = "Some value with ${{RAILWAY_REF}}" + all_secrets = [] + resolved_value = resolve_all_secrets(value, all_secrets, phase, current_application_name, current_env_name) + assert resolved_value == "Some value with ${{RAILWAY_REF}}" + + +def test_railway_syntax_with_env_preserved(phase, current_application_name, current_env_name): + """Railway-style ${{env.key}} should NOT be treated as cross-env reference.""" + value = "${{production.DATABASE_URL}}" + all_secrets = [] + resolved_value = resolve_all_secrets(value, all_secrets, phase, current_application_name, current_env_name) + assert resolved_value == "${{production.DATABASE_URL}}" + + +def test_mixed_railway_and_phase_refs(phase, current_application_name, current_env_name): + """Mix of ${{...}} Railway and ${...} Phase refs - only Phase should be resolved.""" + value = "Railway: ${{RAILWAY_TOKEN}}, Phase: ${KEY}" + all_secrets = [ + {"environment": current_env_name, "path": "/", "key": "KEY", "value": "secret_value"}, + ] + resolved_value = resolve_all_secrets(value, all_secrets, phase, current_application_name, current_env_name) + assert resolved_value == "Railway: ${{RAILWAY_TOKEN}}, Phase: secret_value" + + +def test_secret_value_containing_railway_syntax(phase, current_application_name, current_env_name): + """Secret values containing ${{...}} should preserve the Railway syntax after resolution.""" + value = "${CONFIG}" + all_secrets = [ + {"environment": current_env_name, "path": "/", "key": "CONFIG", "value": "url=${{RAILWAY.STATIC_URL}}"}, + ] + resolved_value = resolve_all_secrets(value, all_secrets, phase, current_application_name, current_env_name) + assert resolved_value == "url=${{RAILWAY.STATIC_URL}}" + + +def test_github_actions_syntax_preserved(phase, current_application_name, current_env_name): + """GitHub Actions ${{ secrets.X }} syntax should be preserved like Railway.""" + value = "${{ secrets.GITHUB_TOKEN }}" + all_secrets = [] + resolved_value = resolve_all_secrets(value, all_secrets, phase, current_application_name, current_env_name) + assert resolved_value == "${{ secrets.GITHUB_TOKEN }}"