From a90383e238b47cdc5ca0fb6e052978d55d266d45 Mon Sep 17 00:00:00 2001 From: krastanov <2611182+vkrastanov@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:09:08 +0300 Subject: [PATCH] create config file with owner-only permissions at both write sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ~/.cf_client_python.json holds the long-lived refresh token, but both write sites created it with a plain open(path, "w") and no permission control, so its mode was silently inherited from the caller's umask — world-readable (0644) under the default umask 022. This is inconsistent with how credential files are normally handled and leaves the token readable by any other local user or process on a shared host. Add a _write_config_file helper and route both write sites through it: import_from_clf_cli() and the interactive first-run path in build_client_from_configuration(). The helper creates the file with os.open(..., 0o600) and unconditionally chmods it to 0600 afterwards, so an existing file left loose by an earlier version gets tightened on the next write as well. Behavior verified before/after: 0644 (unfixed, umask 022) -> 0600 for new files under umask 022/077 and for pre-existing 0644 files, with identical file content in all cases. --- cloudfoundry_client/main/main.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/cloudfoundry_client/main/main.py b/cloudfoundry_client/main/main.py index 6d5b4b7..5b94b63 100644 --- a/cloudfoundry_client/main/main.py +++ b/cloudfoundry_client/main/main.py @@ -56,6 +56,13 @@ def get_config_file() -> str: return os.path.join(get_user_directory(), ".cf_client_python.json") +def _write_config_file(config_file: str, data: dict): + fd = os.open(config_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) + with os.fdopen(fd, "w") as f: + f.write(json.dumps(data, indent=2)) + os.chmod(config_file, 0o600) + + def import_from_clf_cli(): user_directory = get_user_directory() cf_cli_dir = os.path.join(user_directory, ".cf") @@ -68,12 +75,10 @@ def import_from_clf_cli(): cf_cli_data = json.load(cf_cli_file) if cf_cli_data["RefreshToken"] is None or cf_cli_data["Target"] is None: raise IOError("Could not load informations from cf cli configuration") - with open(get_config_file(), "w") as f: - f.write( - json.dumps( - dict(target_endpoint=cf_cli_data["Target"], verify=False, refresh_token=cf_cli_data["RefreshToken"]), indent=2 - ) - ) + _write_config_file( + get_config_file(), + dict(target_endpoint=cf_cli_data["Target"], verify=False, refresh_token=cf_cli_data["RefreshToken"]), + ) def build_client_from_configuration(previous_configuration: dict = None) -> CloudFoundryClient: @@ -95,12 +100,10 @@ def build_client_from_configuration(previous_configuration: dict = None) -> Clou password = _read_value_from_user("Please enter your password") client = CloudFoundryClient(target_endpoint, verify=(verify == "true")) client.init_with_user_credentials(login, password) - with open(config_file, "w") as f: - f.write( - json.dumps( - dict(target_endpoint=target_endpoint, verify=(verify == "true"), refresh_token=client.refresh_token), indent=2 - ) - ) + _write_config_file( + config_file, + dict(target_endpoint=target_endpoint, verify=(verify == "true"), refresh_token=client.refresh_token), + ) return client else: try: