This commit is contained in:
Alex Chen 2026-11-05 07:17:05 +00:00 • committed by GitHub
commit 2c8fd044a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 32 additions and 4 deletions

View file

@ -150,7 +150,7 @@ class Authenticator:
try:
access_token = self._login()
try:
with open(self.access_token_file, "w") as f:
with open(self.access_token_file, "w", opener=self._secure_opener) as f:
f.write(access_token)
except OSError:
verbose_logger.error("Error saving access token to file")
@ -192,7 +192,7 @@ class Authenticator:
) from e
serialized: Final = json.dumps(api_key_info)
try:
with open(self.api_key_file, "w") as f:
with open(self.api_key_file, "w", opener=self._secure_opener) as f:
f.write(serialized)
except OSError as e:
verbose_logger.warning(
@ -289,10 +289,16 @@ class Authenticator:
status_code=401,
)
def _secure_opener(self, path: str, flags: int) -> int:
"""Open with 0o600: file holds a GitHub access token / Copilot API key."""
return os.open(path, flags, 0o600)
def _ensure_token_dir(self) -> None:
"""Ensure the token directory exists."""
if not os.path.exists(self.token_dir):
os.makedirs(self.token_dir, exist_ok=True)
# 0o700: dir holds the GitHub access token + Copilot API key;
# default umask (022/002) would make it group/world-traversable.
os.makedirs(self.token_dir, mode=0o700, exist_ok=True)
def _get_github_headers(self, access_token: str | None = None) -> dict[str, str]:
"""

View file

@ -75,7 +75,29 @@ class TestGitHubCopilotAuthenticator:
patch("os.makedirs") as mock_makedirs,
):
auth = Authenticator()
mock_makedirs.assert_called_once_with(auth.token_dir, exist_ok=True)
# credentials dir must not be group/world-accessible
mock_makedirs.assert_called_once_with(auth.token_dir, mode=0o700, exist_ok=True)
def test_token_dir_and_credential_files_created_with_restrictive_modes(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Token directory is created 0o700 and both credential files are created 0o600 under umask 022."""
token_dir = tmp_path / "copilot_tokens"
monkeypatch.setenv("GITHUB_COPILOT_TOKEN_DIR", str(token_dir))
old_umask = os.umask(0o022)
try:
auth = Authenticator()
assert (os.stat(token_dir).st_mode & 0o777) == 0o700
with patch.object(auth, "_login", return_value="gho_test_token"):
assert auth.get_access_token() == "gho_test_token"
assert (os.stat(auth.access_token_file).st_mode & 0o777) == 0o600
with patch.object(auth, "_refresh_api_key", return_value=dict(REFRESHED_KEY)):
assert auth.get_api_key() == "new-api-key"
assert (os.stat(auth.api_key_file).st_mode & 0o777) == 0o600
finally:
os.umask(old_umask)
def test_get_github_headers(self, authenticator):
"""Test that GitHub headers are correctly generated."""