Gateway MEDIA delivery safe roots for cache paths

NousResearch/hermes-agent · cluster 31733 · gateway

Data as of

Nothing open on GitHub. Fix merged in #34485. Check it covers your case.

The short answer

0open issues
0open PRs
10items grouped
Known?
Yes. 10 related items grouped here, 2 issues already closed.
Fix event
A fix was merged to main in PR #34485. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #54060. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and pull requests address a problem where cached media files (e.g., under cache/images or symlinked HERMES_HOME paths) are not recognized as safe roots for MEDIA delivery in the gateway. The work involves adding canonical cache directory paths to MEDIA_DELIVERY_SAFE_ROOTS and improving diagnostics for related rejections.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #34485 fix(gateway): diagnosable MEDIA rejections + canonical cache roots + null-path guard

    merged to main

    files: gateway/platforms/base.py, tests/gateway/test_platform_base.py

  • #54060 fix(gateway): deliver profile-scoped cache media on symlinked HERMES_HOME

    merged to main

    files: gateway/platforms/base.py, tests/gateway/test_platform_base.py

    Show the diff
    • gateway/platforms/base.py
      @@ -1010,9 +1010,47 @@ def cache_video_from_bytes(data: bytes, ext: str = ".mp4") -> str:
       )
       
       
      +# Canonical cache subdirectories that hold deliverable artifacts. Used both
      +# for the top-level safe roots above and to enumerate per-profile cache roots
      +# at check time (see _media_delivery_allowed_roots).
      +_MEDIA_DELIVERY_CACHE_SUBDIRS = (
      +    "images",
      +    "audio",
      +    "videos",
      +    "documents",
      +    "screenshots",
      +)
      +
      +
      +def _profile_cache_roots() -> List[Path]:
      +    """Return per-profile canonical cache roots under the shared Hermes root.
      +
      +    Profile gateways write generated artifacts to
      +    ``<root>/profiles/<name>/cache/{images,audio,...}``. The static safe-roots
      +    list only covers the *active* HERMES_HOME's cache, so a gateway running at
      +    the root (e.g. ``HERMES_HOME=/opt/data``) while the model emits a
      +    profile-scoped path silently fails delivery. Enumerated dynamically at
      +    check time so profiles created after startup are covered, and so the
      +    resolved profile path is allowlisted *before* the ``/root`` system denylist
      +    is consulted (which otherwise wins when HERMES_HOME is symlinked under a
      +    denied prefix and $HOME is not that prefix). See issue #31733.
      +    """
      +    roots: List[Path] = []
      +    profiles_dir = _HERMES_ROOT / "profiles"
      +    try:
      +        profile_dirs = [p for p in profiles_dir.iterdir() if p.is_dir()]
      +    except OSError:
      +        return roots
      +    for profile_dir in profile_dirs:
      +        for subdir in _MEDIA_DELIVERY_CACHE_SUBDIRS:
      +            roots.append(profile_dir / "cache" / subdir)
      +    return roots
      +
      +
       def _media_delivery_allowed_roots() -> List[Path]:
           """Return roots from which model-emitted local media may be delivered."""
           roots = [Path(root) for root in MEDIA_DELIVERY_SAFE_ROOTS]
      +    roots.extend(_profile_cache_roots())
           extra_roots = os.environ.get(MEDIA_DELIVERY_ALLOW_DIRS_ENV, "")
           for chunk in extra_roots.split(os.pathsep):
               for raw_root in chunk.split(","):
    • tests/gateway/test_platform_base.py
      @@ -1174,6 +1174,69 @@ def test_root_home_hermes_env_still_blocked(self, tmp_path, monkeypatch):
       
               assert BasePlatformAdapter.validate_media_delivery_path(str(env_file)) is None
       
      +    def test_profile_scoped_cache_delivers_under_symlinked_root(self, tmp_path, monkeypatch):
      +        """Reopened #31733: a profile gateway whose HERMES_HOME is symlinked
      +        under a denied prefix (e.g. /opt/data -> /root/.hermes) emits
      +        profile-scoped paths (``<root>/profiles/<name>/cache/images/x.png``)
      +        that resolve under ``/root``. ``$HOME`` is NOT that prefix, so the
      +        root-home exception doesn't fire, and the top-level cache allowlist
      +        doesn't cover the profile subdir — the file was silently dropped.
      +        Per-profile cache roots must be allowlisted so it delivers.
      +        """
      +        self._patch_roots(monkeypatch)  # strict on, zero top-level cache roots
      +
      +        # Stand-in for the literal /root deny prefix in the deployment.
      +        denied_root = tmp_path / "root"
      +        hermes_root = denied_root / ".hermes"
      +        prof_cache = hermes_root / "profiles" / "myprof" / "cache" / "images"
      +        prof_cache.mkdir(parents=True)
      +        image = prof_cache / "gen.png"
      +        image.write_bytes(b"\x89PNG\r\n\x1a\n")
      +
      +        # $HOME is NOT the denied prefix (mirrors HOME=/opt/data/home).
      +        fake_home = tmp_path / "opt" / "data" / "home"
      +        fake_home.mkdir(parents=True)
      +        monkeypatch.setenv("HOME", str(fake_home))
      +        monkeypatch.setattr(
      +            "gateway.platforms.base._MEDIA_DELIVERY_DENIED_PREFIXES",
      +            (str(denied_root),),
      +        )
      +        monkeypatch.setattr(
      +            "gateway.platforms.base._HERMES_ROOT", hermes_root
      +        )
      +
      +        assert (
      +            BasePlatformAdapter.validate_media_delivery_path(str(image))
      +            == str(image.resolve())
      +        )
      +
      +    def test_profile_scoped_credential_still_blocked_under_root(self, tmp_path, monkeypatch):
      +        """The profile-cache allowlist must not un-block a credential sitting
      +        directly in the profile dir (``profiles/<name>/auth.json``): it's not
      +        under a cache subdir, so the credential denylist still rejects it.
      +        """
      +        self._patch_roots(monkeypatch)
      +
      +        denied_root = tmp_path / "root"
      +        hermes_root = denied_root / ".hermes"
      +        prof_dir = hermes_root / "profiles" / "myprof"
      +        prof_dir.mkdir(parents=True)
      +        cred = prof_dir / "auth.json"
      +        cred.write_text("{}")
      +
      +        fake_home = tmp_path / "opt" / "data" / "home"
      +        fake_home.mkdir(parents=True)
      +        monkeypatch.setenv("HOME", str(fake_home))
      +        monkeypatch.setattr(
      +            "gateway.platforms.base._MEDIA_DELIVERY_DENIED_PREFIXES",
      +            (str(denied_root),),
      +        )
      +        monkeypatch.setattr(
      +            "gateway.platforms.base._HERMES_ROOT", hermes_root
      +        )
      +
      +        assert BasePlatformAdapter.validate_media_delivery_path(str(cred)) is None
      +
           def test_other_users_home_still_blocked_for_nonroot(self, tmp_path, monkeypatch):
               """The exception only un-blocks the *running user's own* home. A
               non-root gateway ($HOME=/home/me) must not deliver another user's home

6 PRs closed without merging

From the close comments: done elsewhere 5 · merged via another change 1

  • #31764 fix(gateway): add canonical cache paths to MEDIA_DELIVERY_SAFE_ROOTS · done elsewhere → #34485, #31733, #54060
  • #33238 fix(gateway): add canonical cache paths to MEDIA_DELIVERY_SAFE_ROOTS · done elsewhere → #34485, #31733, #54060
  • #33251 fix(gateway): diagnosable MEDIA delivery + canonical cache roots (#31733) · merged via another change → e28a668, #34485, #31733
  • #33550 fix(gateway): allow canonical media cache delivery roots · done elsewhere → #34485, #31733, #54060
  • #33586 fix(gateway): add canonical cache paths to MEDIA_DELIVERY_SAFE_ROOTS · done elsewhere → #34485, #31733, #54060
Show 1 more
  • #33901 fix: add cache subdirectories to MEDIA delivery safe roots · done elsewhere → #34485, #31733, #54060
Closed issues · 2
  • Closed#31733Generated images under cache/images may not deliver via Telegram MEDIA, while image_cache works
  • Closed#33549Canonical media cache directories should be safe delivery roots
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.23: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
gateway
Merged PRs
#34485, #54060
Closed issues
2
Attempts
6 PRs closed without merging
Fix link checked
28 Jun 2026

For your agent

The same facts, typed.