mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-26 01:01:40 +00:00
fix: hermes auth remove now clears env-seeded credentials permanently (#5285)
Removing an env-seeded credential (e.g. from OPENROUTER_API_KEY) via 'hermes auth' previously had no lasting effect -- the entry was deleted from auth.json but load_pool() re-created it on the next call because the env var was still set. Now auth_remove_command detects env-sourced entries (source starts with 'env:') and calls the new remove_env_value() to strip the var from both .env and os.environ, preventing re-seeding. Changes: - hermes_cli/config.py: add remove_env_value() -- atomically removes a line from .env and pops from os.environ - hermes_cli/auth_commands.py: auth_remove_command clears env var when removing an env-seeded pool entry - 8 new tests covering remove_env_value and the full zombie-credential lifecycle (remove -> reload -> stays gone)
This commit is contained in:
parent
0c95e91059
commit
6ee90a7cf6
4 changed files with 235 additions and 0 deletions
|
|
@ -13,6 +13,7 @@ from hermes_cli.config import (
|
|||
load_config,
|
||||
load_env,
|
||||
migrate_config,
|
||||
remove_env_value,
|
||||
save_config,
|
||||
save_env_value,
|
||||
save_env_value_secure,
|
||||
|
|
@ -149,6 +150,49 @@ class TestSaveEnvValueSecure:
|
|||
assert env_mode == 0o600
|
||||
|
||||
|
||||
class TestRemoveEnvValue:
|
||||
def test_removes_key_from_env_file(self, tmp_path):
|
||||
env_path = tmp_path / ".env"
|
||||
env_path.write_text("KEY_A=value_a\nKEY_B=value_b\nKEY_C=value_c\n")
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path), "KEY_B": "value_b"}):
|
||||
result = remove_env_value("KEY_B")
|
||||
assert result is True
|
||||
content = env_path.read_text()
|
||||
assert "KEY_B" not in content
|
||||
assert "KEY_A=value_a" in content
|
||||
assert "KEY_C=value_c" in content
|
||||
|
||||
def test_clears_os_environ(self, tmp_path):
|
||||
env_path = tmp_path / ".env"
|
||||
env_path.write_text("MY_KEY=my_value\n")
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path), "MY_KEY": "my_value"}):
|
||||
remove_env_value("MY_KEY")
|
||||
assert "MY_KEY" not in os.environ
|
||||
|
||||
def test_returns_false_when_key_not_found(self, tmp_path):
|
||||
env_path = tmp_path / ".env"
|
||||
env_path.write_text("OTHER_KEY=value\n")
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}):
|
||||
result = remove_env_value("MISSING_KEY")
|
||||
assert result is False
|
||||
# File should be untouched
|
||||
assert env_path.read_text() == "OTHER_KEY=value\n"
|
||||
|
||||
def test_handles_missing_env_file(self, tmp_path):
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path), "GHOST_KEY": "ghost"}):
|
||||
result = remove_env_value("GHOST_KEY")
|
||||
assert result is False
|
||||
# os.environ should still be cleared
|
||||
assert "GHOST_KEY" not in os.environ
|
||||
|
||||
def test_clears_os_environ_even_when_not_in_file(self, tmp_path):
|
||||
env_path = tmp_path / ".env"
|
||||
env_path.write_text("OTHER=stuff\n")
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path), "ORPHAN_KEY": "orphan"}):
|
||||
remove_env_value("ORPHAN_KEY")
|
||||
assert "ORPHAN_KEY" not in os.environ
|
||||
|
||||
|
||||
class TestSaveConfigAtomicity:
|
||||
"""Verify save_config uses atomic writes (tempfile + os.replace)."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue