fix(gateway): deliver $HOME deliverables on root-run gateways

Root-run gateways have $HOME=/root, which is on the MEDIA system-path
denylist, so the gateway silently dropped agent-generated deliverables
under /root (e.g. /root/work/proposal.docx) — the user got a 'here is
your file' reply with nothing attached.

_path_under_denied_prefix now treats the running user's own home as
deliverable: the home tree itself is no longer denied, while the
more-specific denied paths inside it (~/.ssh, ~/.aws, ~/.hermes/.env,
auth.json, config.yaml) stay blocked because they are separate denylist
entries. The exception only matches when the denied prefix IS $HOME, so
a non-root gateway still can't deliver another user's home.

Diagnosis, reproduction, and the failing-case analysis are from
@GodsBoy (#38108 / #38106). Implemented here as the minimal denylist
fix rather than a staging/copy subsystem.

Co-authored-by: GodsBoy <dhuysamen@gmail.com>
This commit is contained in:
teknium1
2026-06-04 07:50:22 -07:00
committed by Teknium
co-authored by GodsBoy
parent 580d924097
commit 2982122be7
2 changed files with 138 additions and 3 deletions
+24 -3
View File
@@ -967,14 +967,35 @@ def _media_delivery_denied_paths() -> List[Path]:
def _path_under_denied_prefix(resolved: Path) -> bool:
"""Return True if ``resolved`` lives under a deny-listed system path."""
"""Return True if ``resolved`` lives under a deny-listed system path.
One narrow exception: when a denied prefix IS the running user's own home,
the home itself is not treated as denied. ``/root`` is on the system-path
denylist so that a non-root gateway can't deliver another user's home, but
on a root-run gateway ``$HOME=/root`` and the operator's own deliverables
(``/root/work/proposal.docx``) live directly under it. The credential
sub-directories inside home (``~/.ssh``, ``~/.aws``, ...) and Hermes
secrets (``~/.hermes/.env``, ``auth.json``) are *separate, more-specific*
denied paths, so they stay blocked regardless of this exception — it can
only un-block a plain file sitting in the running user's home tree, never a
credential location or another user's home.
"""
try:
home = Path(os.path.expanduser("~")).resolve(strict=False)
except (OSError, RuntimeError, ValueError):
home = None
for denied in _media_delivery_denied_paths():
try:
resolved_denied = denied.expanduser().resolve(strict=False)
except (OSError, RuntimeError, ValueError):
continue
if _path_is_within(resolved, resolved_denied) or resolved == resolved_denied:
return True
if not (_path_is_within(resolved, resolved_denied) or resolved == resolved_denied):
continue
# Allow the running user's own home tree; its credential sub-dirs are
# caught by their own (more-specific) denylist entries above.
if home is not None and resolved_denied == home:
continue
return True
return False