mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-19 19:25:24 +03:00
fix(cli): save prompted API keys to an owner-only .env
- the key prompt created .env with the default umask, typically readable by other local users - create it 0600 and tighten an existing file before writing the key; a read-only file is still updated
This commit is contained in:
@@ -643,7 +643,11 @@ def ensure_api_key(provider: str) -> str | None:
|
||||
return None
|
||||
|
||||
env_path = find_dotenv(usecwd=True) or str(Path.cwd() / ".env")
|
||||
Path(env_path).touch(exist_ok=True)
|
||||
# The file holds credentials, so make it owner-only before writing: create
|
||||
# it 0600 when absent, and tighten an existing one (set_key keeps the mode).
|
||||
if not os.path.exists(env_path):
|
||||
os.close(os.open(env_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600))
|
||||
os.chmod(env_path, 0o600)
|
||||
set_key(env_path, env_var, key)
|
||||
os.environ[env_var] = key
|
||||
console.print(f"[green]Saved {env_var} to {env_path}[/green]")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import stat
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@@ -146,3 +147,47 @@ def test_ensure_api_key_updates_existing_env_file(monkeypatch, tmp_path, cli_uti
|
||||
assert "OPENAI_API_KEY" in content and "sk-existing" in content
|
||||
assert "OTHER=value" in content
|
||||
assert "OPENROUTER_API_KEY" in content and "sk-openrouter-new" in content
|
||||
|
||||
|
||||
def _prompt_key(cli_utils, monkeypatch, tmp_path, key="sk-typed-in"):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.setattr(cli_utils, "find_dotenv", lambda **k: "")
|
||||
with patch.object(cli_utils, "questionary") as mock_q:
|
||||
mock_q.password.return_value.ask.return_value = key
|
||||
cli_utils.ensure_api_key("openai")
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "nt", reason="POSIX file modes")
|
||||
def test_saved_key_file_is_owner_only(monkeypatch, cli_utils, tmp_path):
|
||||
# The prompt writes a real credential; the file must not be readable by
|
||||
# other local users whatever the umask is.
|
||||
old = os.umask(0o002)
|
||||
try:
|
||||
_prompt_key(cli_utils, monkeypatch, tmp_path)
|
||||
finally:
|
||||
os.umask(old)
|
||||
env = tmp_path / ".env"
|
||||
assert "sk-typed-in" in env.read_text()
|
||||
assert stat.S_IMODE(env.stat().st_mode) == 0o600
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "nt", reason="POSIX file modes")
|
||||
def test_existing_key_file_is_tightened_before_writing(monkeypatch, cli_utils, tmp_path):
|
||||
env = tmp_path / ".env"
|
||||
env.write_text("OTHER=1\n")
|
||||
os.chmod(env, 0o664)
|
||||
_prompt_key(cli_utils, monkeypatch, tmp_path)
|
||||
assert stat.S_IMODE(env.stat().st_mode) == 0o600
|
||||
assert "OTHER=1" in env.read_text()
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "nt", reason="POSIX file modes")
|
||||
def test_read_only_key_file_is_still_updated(monkeypatch, cli_utils, tmp_path):
|
||||
env = tmp_path / ".env"
|
||||
env.write_text("OTHER=1\n")
|
||||
os.chmod(env, 0o400)
|
||||
_prompt_key(cli_utils, monkeypatch, tmp_path)
|
||||
assert "sk-typed-in" in env.read_text()
|
||||
assert stat.S_IMODE(env.stat().st_mode) == 0o600
|
||||
|
||||
Reference in New Issue
Block a user