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:
@@ -61,6 +61,9 @@ _EXCLUDED_NAMES = {
|
||||
"cron.pid",
|
||||
}
|
||||
|
||||
# zipfile.open() drops Unix mode bits on extract; restore tightens these to 0600.
|
||||
_SECRET_FILE_NAMES = {".env", "auth.json", "state.db"}
|
||||
|
||||
|
||||
def _should_exclude(rel_path: Path) -> bool:
|
||||
"""Return True if *rel_path* (relative to hermes root) should be skipped."""
|
||||
@@ -381,6 +384,8 @@ def run_import(args) -> None:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zf.open(member) as src, open(target, "wb") as dst:
|
||||
dst.write(src.read())
|
||||
if target.name in _SECRET_FILE_NAMES:
|
||||
os.chmod(target, 0o600)
|
||||
restored += 1
|
||||
except (PermissionError, OSError) as exc:
|
||||
errors.append(f" {rel}: {exc}")
|
||||
|
||||
Reference in New Issue
Block a user