Skip to content

Commit eb6f5d2

Browse files
committed
fix(config): handle corrupted config.toml in get_credentials and set_credentials
get_credentials() called toml.load() without exception handling, causing a raw TOMLDecodeError to crash any code that imports the runpod package when ~/.runpod/config.toml is malformed. Return None on parse failure, matching the existing pattern in check_credentials().
1 parent 8d08f24 commit eb6f5d2

2 files changed

Lines changed: 63 additions & 7 deletions

File tree

runpod/cli/groups/config/functions.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@ def set_credentials(api_key: str, profile: str = "default", overwrite=False) ->
3131
Path(CREDENTIAL_FILE).touch(exist_ok=True)
3232

3333
if not overwrite:
34-
with open(CREDENTIAL_FILE, "rb") as cred_file:
35-
if profile in toml.load(cred_file):
36-
raise ValueError(
37-
"Profile already exists. Use `update_credentials` instead."
38-
)
34+
try:
35+
with open(CREDENTIAL_FILE, "rb") as cred_file:
36+
existing = toml.load(cred_file)
37+
except (TypeError, ValueError):
38+
existing = {}
39+
if profile in existing:
40+
raise ValueError(
41+
"Profile already exists. Use `update_credentials` instead."
42+
)
3943

4044
with open(CREDENTIAL_FILE, "w", encoding="UTF-8") as cred_file:
4145
cred_file.write("[" + profile + "]\n")
@@ -72,12 +76,18 @@ def check_credentials(profile: str = "default"):
7276
def get_credentials(profile="default"):
7377
"""
7478
Returns the credentials for the specified profile from ~/.runpod/config.toml
79+
80+
Returns None if the file does not exist, is not valid TOML, or does not
81+
contain the requested profile.
7582
"""
7683
if not os.path.exists(CREDENTIAL_FILE):
7784
return None
7885

79-
with open(CREDENTIAL_FILE, "rb") as cred_file:
80-
credentials = toml.load(cred_file)
86+
try:
87+
with open(CREDENTIAL_FILE, "rb") as cred_file:
88+
credentials = toml.load(cred_file)
89+
except (TypeError, ValueError):
90+
return None
8191

8292
if profile not in credentials:
8393
return None

tests/test_cli/test_cli_groups/test_config_functions.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,49 @@ def test_get_credentials_non_existent_profile(
9797
assert result is None
9898
assert mock_open_call.called
9999
assert mock_exists.called
100+
101+
@patch("os.path.exists", return_value=True)
102+
@patch(
103+
"runpod.cli.groups.config.functions.toml.load",
104+
side_effect=ValueError("Invalid value"),
105+
)
106+
@patch("builtins.open", new_callable=mock_open)
107+
def test_get_credentials_corrupted_toml(
108+
self, _mock_open_call, _mock_toml_load, _mock_exists
109+
):
110+
"""get_credentials returns None when config.toml contains invalid TOML."""
111+
result = functions.get_credentials("default")
112+
assert result is None
113+
114+
@patch("os.path.exists", return_value=True)
115+
@patch(
116+
"runpod.cli.groups.config.functions.toml.load",
117+
side_effect=TypeError("bad type"),
118+
)
119+
@patch("builtins.open", new_callable=mock_open)
120+
def test_get_credentials_type_error(
121+
self, _mock_open_call, _mock_toml_load, _mock_exists
122+
):
123+
"""get_credentials returns None on TypeError from corrupted file."""
124+
result = functions.get_credentials("default")
125+
assert result is None
126+
127+
@patch("runpod.cli.groups.config.functions.toml.load")
128+
@patch("builtins.open", new_callable=mock_open())
129+
def test_set_credentials_corrupted_toml_allows_overwrite(
130+
self, _mock_file, mock_toml_load
131+
):
132+
"""set_credentials with overwrite=True ignores corrupted existing file."""
133+
mock_toml_load.side_effect = ValueError("Invalid TOML")
134+
# overwrite=True skips the toml.load check entirely
135+
functions.set_credentials("NEW_KEY", overwrite=True)
136+
137+
@patch("runpod.cli.groups.config.functions.toml.load")
138+
@patch("builtins.open", new_callable=mock_open())
139+
def test_set_credentials_corrupted_toml_no_overwrite(
140+
self, _mock_file, mock_toml_load
141+
):
142+
"""set_credentials without overwrite treats corrupted file as empty."""
143+
mock_toml_load.side_effect = ValueError("Invalid TOML")
144+
# Should not raise — corrupted file is treated as having no profiles
145+
functions.set_credentials("NEW_KEY", overwrite=False)

0 commit comments

Comments
 (0)