From 4cd0a461603f509e1949e9162d414b20c6413465 Mon Sep 17 00:00:00 2001 From: hordunlarmy Date: Thu, 29 Jan 2026 18:18:01 +0100 Subject: [PATCH] fix(env_manager): Use base64 encoding for robust .env file content writing --- changelogs/2026-01-29_17-52-23.md | 2 +- src/env_manager.py | 15 ++++++++++----- tests/unit/test_env_manager.py | 21 +++++++++++++-------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/changelogs/2026-01-29_17-52-23.md b/changelogs/2026-01-29_17-52-23.md index b7cd9b4..814edf3 100644 --- a/changelogs/2026-01-29_17-52-23.md +++ b/changelogs/2026-01-29_17-52-23.md @@ -9,4 +9,4 @@ - Changed the `create_env_file` function to only create directories without changing their permissions, avoiding potential permission errors. ### Fixed -- No explicit bug fixes mentioned, but several changes improve the handling of environment variables and secrets, potentially fixing related issues. \ No newline at end of file +- No explicit bug fixes mentioned, but several changes improve the handling of environment variables and secrets, potentially fixing related issues. diff --git a/src/env_manager.py b/src/env_manager.py index 579e14e..c73658f 100644 --- a/src/env_manager.py +++ b/src/env_manager.py @@ -1,3 +1,4 @@ +import base64 import json import os import re @@ -6,6 +7,7 @@ import yaml from src import config +from src.connection import run_command def parse_all_in_one_secret(secret_content: str, format_hint: str = "auto") -> Dict[str, str]: @@ -368,16 +370,19 @@ def detect_environment_secrets() -> Dict[str, Dict[str, str]]: def create_env_file(conn, file_path: str, env_vars: Dict[str, str]) -> None: - """Create .env file with secure permissions (0o600)""" + """Create .env file with secure permissions (0o600) via run_command (supports sudo)""" if not env_vars: return dir_path = os.path.dirname(file_path) if dir_path and dir_path != file_path: - # Only mkdir, skip chmod on directory to avoid permission errors if it exists/owned by others - conn.run(f"mkdir -p {dir_path}") + # Only mkdir, skip chmod on directory to avoid permission errors + run_command(conn, f"mkdir -p {dir_path}") + env_content = "\n".join([f"{k}={v}" for k, v in env_vars.items()]) - conn.run(f"cat > \"{file_path}\" << 'EOF'\n{env_content}\nEOF") - conn.run(f'chmod 644 "{file_path}"') + # Use base64 to avoid shell character/newline mangling issues + encoded = base64.b64encode(env_content.encode("utf-8")).decode("utf-8") + run_command(conn, f"echo '{encoded}' | base64 -d | tee \"{file_path}\" > /dev/null") + run_command(conn, f'chmod 600 "{file_path}"') def generate_env_files(conn) -> None: diff --git a/tests/unit/test_env_manager.py b/tests/unit/test_env_manager.py index 0c32013..7478e37 100644 --- a/tests/unit/test_env_manager.py +++ b/tests/unit/test_env_manager.py @@ -80,11 +80,16 @@ def test_root_mega_file_creation(mock_conn, monkeypatch): def test_heredoc_escaping(mock_conn): env_vars = {"KEY": "val$with$dollars"} - create_env_file(mock_conn, ".env", env_vars) - found_secure_cat = False - for call in mock_conn.run.call_args_list: - cmd = call[0][0] - if "cat >" in cmd and "<< 'EOF'" in cmd: - found_secure_cat = True - assert "KEY=val$with$dollars" in cmd - assert found_secure_cat + # We need to mock run_command because create_env_file now uses it + with patch("src.env_manager.run_command") as mock_run_cmd: + create_env_file(mock_conn, ".env", env_vars) + + found_base64_tee = False + for call in mock_run_cmd.call_args_list: + cmd = call[0][1] + if "base64 -d" in cmd and "tee" in cmd: + found_base64_tee = True + # The content should be base64 encoded + # KEY=val$with$dollars -> S0VZPXZhbCR3aXRoJGRvbGxhcnM= + assert "S0VZPXZhbCR3aXRoJGRvbGxhcnM=" in cmd + assert found_base64_tee