feat(file_safety): support multiple HERMES_WRITE_SAFE_ROOT dirs

Supports multiple directories separated by ':' (Unix PATH-style).
E.g., HERMES_WRITE_SAFE_ROOT=/opt/data:/var/www/html

Fixes #49535
This commit is contained in:
Zheng Tao 2026-06-20 17:28:46 +08:00 committed by kshitij
parent a67ddf5983
commit fa8f1517da

View file

@ -77,15 +77,21 @@ def build_write_denied_prefixes(home: str) -> list[str]:
]
def get_safe_write_root() -> Optional[str]:
"""Return the resolved HERMES_WRITE_SAFE_ROOT path, or None if unset."""
root = os.getenv("HERMES_WRITE_SAFE_ROOT", "")
if not root:
return None
try:
return os.path.realpath(os.path.expanduser(root))
except Exception:
return None
def get_safe_write_roots() -> set[str]:
"""Return resolved HERMES_WRITE_SAFE_ROOT paths. Supports multiple directories
separated by ':' (Unix PATH-style). E.g., '/opt/data:/var/www/html'."""
env = os.getenv("HERMES_WRITE_SAFE_ROOT", "")
if not env:
return set()
roots: set[str] = set()
for path in env.split(":"):
if path:
try:
resolved = os.path.realpath(os.path.expanduser(path))
roots.add(resolved)
except Exception:
continue
return roots
def is_write_denied(path: str) -> bool:
@ -124,9 +130,16 @@ def is_write_denied(path: str) -> bool:
except Exception:
pass
safe_root = get_safe_write_root()
if safe_root and not (resolved == safe_root or resolved.startswith(safe_root + os.sep)):
return True
safe_roots = get_safe_write_roots()
if safe_roots:
# Allow write if path is under ANY of the safe roots
allowed = False
for safe_root in safe_roots:
if resolved == safe_root or resolved.startswith(safe_root + os.sep):
allowed = True
break
if not allowed:
return True
return False