security: enforce 0600/0700 file permissions on sensitive files (inspired by openclaw)
Enforce owner-only permissions on files and directories that contain secrets or sensitive data: - cron/jobs.py: jobs.json (0600), cron dirs (0700), job output files (0600) - hermes_cli/config.py: config.yaml (0600), .env (0600), ~/.hermes/* dirs (0700) - cli.py: config.yaml via save_config_value (0600) All chmod calls use try/except for Windows compatibility. Includes _secure_file() and _secure_dir() helpers with graceful fallback. 8 new tests verify permissions on all file types. Inspired by openclaw v2026.3.7 file permission enforcement.
This commit is contained in:
parent
a2d0d07109
commit
0ce190be0d
4 changed files with 190 additions and 6 deletions
|
|
@ -46,13 +46,32 @@ def get_project_root() -> Path:
|
|||
"""Get the project installation directory."""
|
||||
return Path(__file__).parent.parent.resolve()
|
||||
|
||||
def _secure_dir(path):
|
||||
"""Set directory to owner-only access (0700). No-op on Windows."""
|
||||
try:
|
||||
os.chmod(path, 0o700)
|
||||
except (OSError, NotImplementedError):
|
||||
pass
|
||||
|
||||
|
||||
def _secure_file(path):
|
||||
"""Set file to owner-only read/write (0600). No-op on Windows."""
|
||||
try:
|
||||
if os.path.exists(str(path)):
|
||||
os.chmod(path, 0o600)
|
||||
except (OSError, NotImplementedError):
|
||||
pass
|
||||
|
||||
|
||||
def ensure_hermes_home():
|
||||
"""Ensure ~/.hermes directory structure exists."""
|
||||
"""Ensure ~/.hermes directory structure exists with secure permissions."""
|
||||
home = get_hermes_home()
|
||||
(home / "cron").mkdir(parents=True, exist_ok=True)
|
||||
(home / "sessions").mkdir(parents=True, exist_ok=True)
|
||||
(home / "logs").mkdir(parents=True, exist_ok=True)
|
||||
(home / "memories").mkdir(parents=True, exist_ok=True)
|
||||
home.mkdir(parents=True, exist_ok=True)
|
||||
_secure_dir(home)
|
||||
for subdir in ("cron", "sessions", "logs", "memories"):
|
||||
d = home / subdir
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
_secure_dir(d)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -808,6 +827,7 @@ def save_config(config: Dict[str, Any]):
|
|||
sections.append("fallback")
|
||||
if sections:
|
||||
f.write(_COMMENTED_SECTIONS)
|
||||
_secure_file(config_path)
|
||||
|
||||
|
||||
def load_env() -> Dict[str, str]:
|
||||
|
|
@ -860,6 +880,7 @@ def save_env_value(key: str, value: str):
|
|||
|
||||
with open(env_path, 'w', **write_kw) as f:
|
||||
f.writelines(lines)
|
||||
_secure_file(env_path)
|
||||
|
||||
|
||||
def get_env_value(key: str) -> Optional[str]:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue