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
- 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
Open PRs0
Fixes on record
-
#34485 fix(gateway): diagnosable MEDIA rejections + canonical cache roots + null-path guard
files:
gateway/platforms/base.py,tests/gateway/test_platform_base.py -
#54060 fix(gateway): deliver profile-scoped cache media on symlinked HERMES_HOME
files:
gateway/platforms/base.py,tests/gateway/test_platform_base.pyShow 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
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.00means 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