mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat: secure skill env setup on load (core #688)
When a skill declares required_environment_variables in its YAML frontmatter, missing env vars trigger a secure TUI prompt (identical to the sudo password widget) when the skill is loaded. Secrets flow directly to ~/.hermes/.env, never entering LLM context. Key changes: - New required_environment_variables frontmatter field for skills - Secure TUI widget (masked input, 120s timeout) - Gateway safety: messaging platforms show local setup guidance - Legacy prerequisites.env_vars normalized into new format - Remote backend handling: conservative setup_needed=True - Env var name validation, file permissions hardened to 0o600 - Redact patterns extended for secret-related JSON fields - 12 existing skills updated with prerequisites declarations - ~48 new tests covering skip, timeout, gateway, remote backends - Dynamic panel widget sizing (fixes hardcoded width from original PR) Cherry-picked from PR #723 by kshitijk4poor, rebased onto current main with conflict resolution. Fixes #688 Co-authored-by: kshitijk4poor <kshitijk4poor@users.noreply.github.com>
This commit is contained in:
parent
c097e56142
commit
ccfbf42844
34 changed files with 2419 additions and 403 deletions
|
|
@ -6,14 +6,15 @@ from unittest.mock import patch, MagicMock
|
|||
|
||||
import yaml
|
||||
|
||||
import yaml
|
||||
|
||||
from hermes_cli.config import (
|
||||
DEFAULT_CONFIG,
|
||||
get_hermes_home,
|
||||
ensure_hermes_home,
|
||||
load_config,
|
||||
load_env,
|
||||
save_config,
|
||||
save_env_value,
|
||||
save_env_value_secure,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -94,6 +95,43 @@ class TestSaveAndLoadRoundtrip:
|
|||
assert reloaded["terminal"]["timeout"] == 999
|
||||
|
||||
|
||||
class TestSaveEnvValueSecure:
|
||||
def test_save_env_value_writes_without_stdout(self, tmp_path, capsys):
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}):
|
||||
save_env_value("TENOR_API_KEY", "sk-test-secret")
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == ""
|
||||
assert captured.err == ""
|
||||
|
||||
env_values = load_env()
|
||||
assert env_values["TENOR_API_KEY"] == "sk-test-secret"
|
||||
|
||||
def test_secure_save_returns_metadata_only(self, tmp_path):
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}):
|
||||
result = save_env_value_secure("GITHUB_TOKEN", "ghp_test_secret")
|
||||
assert result == {
|
||||
"success": True,
|
||||
"stored_as": "GITHUB_TOKEN",
|
||||
"validated": False,
|
||||
}
|
||||
assert "secret" not in str(result).lower()
|
||||
|
||||
def test_save_env_value_updates_process_environment(self, tmp_path):
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}, clear=False):
|
||||
os.environ.pop("TENOR_API_KEY", None)
|
||||
save_env_value("TENOR_API_KEY", "sk-test-secret")
|
||||
assert os.environ["TENOR_API_KEY"] == "sk-test-secret"
|
||||
|
||||
def test_save_env_value_hardens_file_permissions_on_posix(self, tmp_path):
|
||||
if os.name == "nt":
|
||||
return
|
||||
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}):
|
||||
save_env_value("TENOR_API_KEY", "sk-test-secret")
|
||||
env_mode = (tmp_path / ".env").stat().st_mode & 0o777
|
||||
assert env_mode == 0o600
|
||||
|
||||
|
||||
class TestSaveConfigAtomicity:
|
||||
"""Verify save_config uses atomic writes (tempfile + os.replace)."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue