From 837c2dce15caecd2705e362da77b4bf8c009eec8 Mon Sep 17 00:00:00 2001 From: bibi samina Date: Mon, 14 Sep 2026 13:36:02 +0530 Subject: [PATCH] fix(google-auth-oauthlib): create saved credentials file with mode 0600 --- .../google_auth_oauthlib/tool/__main__.py | 7 +++-- .../tests/unit/test_tool.py | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/google-auth-oauthlib/google_auth_oauthlib/tool/__main__.py b/packages/google-auth-oauthlib/google_auth_oauthlib/tool/__main__.py index 297d9f62c056..eea6d3da30a3 100644 --- a/packages/google-auth-oauthlib/google_auth_oauthlib/tool/__main__.py +++ b/packages/google-auth-oauthlib/google_auth_oauthlib/tool/__main__.py @@ -107,9 +107,12 @@ def main(client_secrets, scope, save, credentials): config_path = os.path.dirname(credentials) if config_path and not os.path.isdir(config_path): - os.makedirs(config_path) + os.makedirs(config_path, mode=0o700) - with open(credentials, "w") as outfile: + # The file holds the refresh token and client secret, so restrict it + # to the current user instead of relying on the process umask. + fd = os.open(credentials, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) + with os.fdopen(fd, "w") as outfile: json.dump(creds_data, outfile) click.echo("credentials saved: %s" % credentials) diff --git a/packages/google-auth-oauthlib/tests/unit/test_tool.py b/packages/google-auth-oauthlib/tests/unit/test_tool.py index dd4fd4886981..7fc82f740f5c 100644 --- a/packages/google-auth-oauthlib/tests/unit/test_tool.py +++ b/packages/google-auth-oauthlib/tests/unit/test_tool.py @@ -15,6 +15,7 @@ import io import json import os.path +import stat import tempfile from unittest import mock @@ -131,3 +132,33 @@ def test_save_existing_dir(self, runner, local_server_mock): assert not result.exception assert "saved" in result.output assert result.exit_code == 0 + + @pytest.mark.skipif( + os.name == "nt", reason="POSIX file permissions are not available on Windows" + ) + def test_save_file_permissions(self, runner, local_server_mock): + credentials_tmpdir = tempfile.mkdtemp() + credentials_path = os.path.join( + credentials_tmpdir, "new-directory", "credentials.json" + ) + result = runner.invoke( + cli.main, + [ + "--client-secrets", + CLIENT_SECRETS_FILE, + "--scope", + "somescope", + "--credentials", + credentials_path, + "--save", + ], + ) + local_server_mock.assert_called_with(mock.ANY) + assert not result.exception + assert result.exit_code == 0 + # The saved file holds the refresh token and client secret, so neither + # it nor the directory created for it may be group or world accessible. + file_mode = stat.S_IMODE(os.stat(credentials_path).st_mode) + dir_mode = stat.S_IMODE(os.stat(os.path.dirname(credentials_path)).st_mode) + assert file_mode & 0o077 == 0 + assert dir_mode & 0o077 == 0