mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-07 02:51:50 +00:00
fix(security): restore .env/auth.json/state.db with 0600 perms
`hermes import` was creating secret files with the process umask (typically 0644) instead of 0600. zipfile.open() does not honor the Unix mode bits stored in zip member external_attr; the restore loop used open(target, "wb") which always falls back to umask. Threat: silent privilege downgrade after a routine restore on multi-user systems (shared dev boxes, CI runners, jump hosts) — any local user could read API keys and OAuth tokens from ~/.hermes/. Fix mirrors the convention already used at file creation (hermes_cli/auth.py: stat.S_IRUSR | stat.S_IWUSR for auth.json). The quick-snapshot restore path (restore_quick_snapshot) is unaffected — it uses shutil.copy2 which preserves perms via copystat(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
da8654bb41
commit
60c4bc96fd
2 changed files with 31 additions and 0 deletions
|
|
@ -471,6 +471,32 @@ class TestImport:
|
|||
with pytest.raises(SystemExit):
|
||||
run_import(args)
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="POSIX file permissions only")
|
||||
def test_restores_secret_files_with_0600_perms(self, tmp_path, monkeypatch):
|
||||
"""Secret files must end up at 0600 after restore (zipfile drops mode bits)."""
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
||||
|
||||
zip_path = tmp_path / "backup.zip"
|
||||
self._make_backup_zip(zip_path, {
|
||||
"config.yaml": "model: openrouter\n",
|
||||
".env": "OPENROUTER_API_KEY=sk-secret\n",
|
||||
"auth.json": '{"providers": {"nous": "token"}}',
|
||||
"state.db": b"SQLite format 3\x00",
|
||||
"profiles/coder/.env": "ANTHROPIC_API_KEY=sk-ant-secret\n",
|
||||
})
|
||||
|
||||
args = Namespace(zipfile=str(zip_path), force=True)
|
||||
|
||||
from hermes_cli.backup import run_import
|
||||
run_import(args)
|
||||
|
||||
for rel in (".env", "auth.json", "state.db", "profiles/coder/.env"):
|
||||
mode = (hermes_home / rel).stat().st_mode & 0o777
|
||||
assert mode == 0o600, f"{rel} restored with mode {oct(mode)}, expected 0o600"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Round-trip test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue