{"repo":"NousResearch/hermes-agent","public_id":"42202","display_label":"Expanding supported media file type extensions","summary":"These issues and pull requests concern adding support for additional file extensions (such as .py, .markdown, .stl, .3mf) to media delivery handling in the gateway. The work centers on ensuring non-standard or code-related file types are recognized and transmitted correctly as attachments.","subsystem":"gateway","counts":{"members":6,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":2,"merged_prs":1,"attempt_prs":3},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":65510},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-07-16T11:23:59+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.2906666666666667},"fix_edges":[{"issue_number":35474,"pr_number":65510,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":37318,"pr_number":65510,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":42206,"pr_number":65510,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":53249,"pr_number":65510,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":42202,"title":"send_message + MEDIA: 对 .py 文件发送失败","url":null,"state":"closed"},{"kind":"issue","number":42206,"title":"[Bug]: send_message + MEDIA: fails to send .py files as attachments on Telegram","url":null,"state":"closed"},{"kind":"pr","number":42226,"title":"fix(gateway): add code file extensions to MEDIA_DELIVERY_EXTS","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#65510","number":65510},{"kind":"pr","value":"#42206","number":42206}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":42374,"title":"fix(media): add code file extensions to MEDIA_DELIVERY_EXTS (#42206)","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":60069,"title":"fix(gateway): allow .py/.markdown/.stl/.3mf media & document attachments","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#65510","number":65510}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":65510,"title":"fix(gateway): deliver MEDIA: tags for every file type via validated egress","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["gateway/platforms/base.py","tests/gateway/test_platform_base.py"],"fix_hunks":[{"file":"gateway/platforms/base.py","hunks":["@@ -1504,12 +1504,16 @@ def _log_safe_path(path: str) -> str:\n     re.IGNORECASE,\n )\n \n-# Extension-less absolute paths (e.g. Caddyfile, Dockerfile, Makefile) are\n-# intentionally excluded from MEDIA_TAG_CLEANUP_RE — they are validated and\n-# delivered via MEDIA_EXTENSIONLESS_TAG_RE so prompt-injection paths that do\n-# not exist on disk are left visible instead of silently dropped. Paths with\n-# an unknown but present extension (e.g. .weirdext) stay on the #34517\n-# bare-path fallback and are not handled here.\n+# Paths NOT covered by MEDIA_TAG_CLEANUP_RE's extension alternation — both\n+# extension-less files (Caddyfile, Dockerfile, Makefile) and files with an\n+# unknown extension (.py, .log, .weirdext, ...) — are validated and delivered\n+# via MEDIA_EXTENSIONLESS_TAG_RE. Every ``MEDIA:`` path is therefore\n+# deliverable regardless of file type (#36060): known extensions extract\n+# unconditionally via the anchored pattern above, everything else extracts\n+# only after ``validate_media_delivery_path`` accepts it (exists on disk, not\n+# under the credential/system denylist, strict-mode rules honored), so\n+# prompt-injection paths that do not validate are left visible instead of\n+# silently dropped.\n MEDIA_EXTENSIONLESS_TAG_RE = re.compile(\n     r'''[`\"']?MEDIA:\\s*'''\n     r'''(?P<path>`[^`\\n]+`|\"[^\"\\n]+\"|'[^'\\n]+'|'''","@@ -1527,8 +1531,16 @@ def _normalize_media_tag_path(raw: str) -> str:\n \n \n def _path_lacks_deliverable_extension(path: str) -> bool:\n-    \"\"\"True only when the basename has no extension (Caddyfile, Makefile, …).\"\"\"\n-    return not Path(path).suffix\n+    \"\"\"True when MEDIA_TAG_CLEANUP_RE's extension alternation does not cover\n+    ``path`` — either the basename has no extension at all (Caddyfile,\n+    Makefile, …) or the extension is not in MEDIA_DELIVERY_EXTS (.py, .log,\n+    .weirdext, …). Such paths route through the validated delivery pass\n+    (``validate_media_delivery_path``) instead of the unconditional one, so\n+    every file type is deliverable (#36060) while nonexistent / denylisted\n+    paths stay visible in the text.\n+    \"\"\"\n+    suffix = Path(path).suffix.lower()\n+    return not suffix or suffix not in MEDIA_DELIVERY_EXTS\n \n \n def _strip_media_tag_directives(text: str) -> str:"]},{"file":"tests/gateway/test_platform_base.py","hunks":["@@ -631,12 +631,15 @@ def test_extract_media_and_local_files_share_one_extension_set(self):\n \n     def test_unknown_extension_not_black_holed_by_cleanup(self):\n         \"\"\"A MEDIA: tag with an unknown extension is NOT stripped from the\n-        body — it survives so extract_local_files can still see the bare path,\n-        rather than vanishing entirely (the core of issue #34517).\"\"\"\n+        body by the extension-anchored cleanup — and when the path does not\n+        validate (nonexistent file here), it is not delivered either, so the\n+        tag survives visibly instead of vanishing (the core of issue #34517).\n+        Validated unknown-extension paths DO deliver — see\n+        TestUniversalMediaEgress (#36060).\"\"\"\n         from gateway.platforms.base import MEDIA_TAG_CLEANUP_RE\n         text = \"Saved to MEDIA:/tmp/data.weirdext done\"\n         media, _ = BasePlatformAdapter.extract_media(text)\n-        assert media == []  # unknown extension is not a deliverable MEDIA tag\n+        assert media == []  # nonexistent path fails validation, not delivered\n         stripped = MEDIA_TAG_CLEANUP_RE.sub(\"\", text)\n         assert \"/tmp/data.weirdext\" in stripped  # path preserved, not dropped\n ","@@ -712,6 +715,104 @@ def test_as_document_directive_stripped_without_media_tag(self):\n         )\n \n \n+class TestUniversalMediaEgress:\n+    \"\"\"#36060: every MEDIA: path is deliverable regardless of file type.\n+\n+    Known extensions extract unconditionally (MEDIA_TAG_CLEANUP_RE); unknown\n+    extensions and extension-less files extract via the validated pass —\n+    delivered when validate_media_delivery_path accepts them, left visible\n+    when it does not (nonexistent, denylisted).\n+    \"\"\"\n+\n+    def _patch_allow_root(self, monkeypatch, root):\n+        monkeypatch.setattr(\n+            \"gateway.platforms.base.MEDIA_DELIVERY_SAFE_ROOTS\",\n+            (str(root),),\n+        )\n+        monkeypatch.delenv(\"HERMES_MEDIA_DELIVERY_STRICT\", raising=False)\n+\n+    @pytest.mark.parametrize(\"name\", [\n+        \"script.py\", \"server.log\", \"notes.weirdext\", \"app.ts\", \"run.sh\",\n+        \"config.toml\", \"styles.css\", \"contract.sol\",\n+    ])\n+    def test_unknown_extension_delivered_when_file_validates(\n+        self, tmp_path, monkeypatch, name,\n+    ):\n+        root = tmp_path / \"output\"\n+        root.mkdir()\n+        f = root / name\n+        f.write_text(\"content\", encoding=\"utf-8\")\n+        self._patch_allow_root(monkeypatch, root)\n+\n+        content = f\"Here you go:\\nMEDIA:{f}\\nDone.\"\n+        media, cleaned = BasePlatformAdapter.extract_media(content)\n+        assert len(media) == 1\n+        assert media[0][0] == str(f.resolve())\n+        assert \"MEDIA:\" not in cleaned\n+        assert \"Done.\" in cleaned\n+\n+    def test_unknown_extension_left_visible_when_not_on_disk(\n+        self, tmp_path, monkeypatch,\n+    ):\n+        root = tmp_path / \"output\"\n+        root.mkdir()\n+        self._patch_allow_root(monkeypatch, root)\n+\n+        content = \"MEDIA:/nonexistent/script.py\"\n+        media, cleaned = BasePlatformAdapter.extract_media(content)\n+        assert media == []\n+        assert \"MEDIA:/nonexistent/script.py\" in cleaned\n+\n+    def test_denylisted_paths_still_rejected_regardless_of_extension(\n+        self, tmp_path, monkeypatch,\n+    ):\n+        # A denylisted path must not deliver even though .py/.log/etc now\n+        # route through the validated pass. _media_delivery_denied_paths()\n+        # reads _MEDIA_DELIVERY_DENIED_PREFIXES at call time, so patching the\n+        # tuple exercises the real denylist logic.\n+        secret_dir = [redacted:secret-assignment] / \"secrets\"\n+        secret_dir.mkdir()\n+        f = secret_dir / \"creds.py\"\n+        f.write_text(\"TOKEN = 'x'\", encoding=\"utf-8\")\n+        monkeypatch.setattr(\n+            \"gateway.platforms.base._MEDIA_DELIVERY_DENIED_PREFIXES\",\n+            (str(secret_dir),),\n+        )\n+        monkeypatch.delenv(\"HERMES_MEDIA_DELIVERY_STRICT\", raising=False)\n+\n+        content = f\"MEDIA:{f}\"\n+        media, cleaned = BasePlatformAdapter.extract_media(content)\n+        assert media == []\n+        assert \"MEDIA:\" in cleaned  # rejected tag stays visible\n+\n+    def test_strip_for_display_strips_validated_unknown_extension(\n+        self, tmp_path, monkeypatch,\n+    ):\n+        root = tmp_path / \"output\"\n+        root.mkdir()\n+        f = root / \"server.log\"\n+        f.write_text(\"x\", encoding=\"utf-8\")\n+        self._patch_allow_root(monkeypatch, root)\n+\n+        text = f\"MEDIA:{f}\"\n+        stripped = BasePlatformAdapter.strip_media_directives_for_display(text)\n+        assert \"MEDIA:\" not in stripped\n+\n+    def test_strip_for_display_keeps_unvalidated_unknown_extension(self):\n+        text = \"MEDIA:/nonexistent/server.log\"\n+        stripped = BasePlatformAdapter.strip_media_directives_for_display(text)\n+        assert \"MEDIA:/nonexistent/server.log\" in stripped\n+\n+    def test_known_extension_still_unconditional(self):\n+        # Known extensions keep the pre-#36060 behavior: extracted (and the\n+        # tag stripped) even when the file does not exist — downstream\n+        # delivery surfaces the failure.\n+        content = \"MEDIA:/nonexistent/report.pdf\"\n+        media, cleaned = BasePlatformAdapter.extract_media(content)\n+        assert media == [(\"/nonexistent/report.pdf\", False)]\n+        assert \"MEDIA:\" not in cleaned\n+\n+\n class TestMediaDeliveryPathValidation:\n     def _patch_roots(self, monkeypatch, *roots):\n         monkeypatch.setattr(\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}