# NousResearch/hermes-agent · Gateway support for .stl/.3mf and other attachment types

## Open now

- Nothing open on GitHub.

These items concern the gateway's handling of file attachments, mainly .stl and .3mf uploads (one reports Telegram rejecting them), with related changes touching .py/.markdown types and MEDIA: tag delivery through validated egress. The members appear to share a theme of expanding or fixing accepted attachment file types, though their exact scopes differ.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #65510. Verify it addresses your case.
- **Recommended:** read PR #65510 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member 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.

## Closed / duplicate issues

- #53249: Telegram gateway rejects .stl / .3mf attachments — silently breaks 3D-printing agent workflows

## Prior & competing PRs

- **#65510: fix(gateway): deliver MEDIA: tags for every file type via validated egress**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `gateway/platforms/base.py`, `tests/gateway/test_platform_base.py`
  - fix hunks:
  - `gateway/platforms/base.py`

    ```diff
    @@ -1504,12 +1504,16 @@ def _log_safe_path(path: str) -> str:
         re.IGNORECASE,
     )
     
    -# Extension-less absolute paths (e.g. Caddyfile, Dockerfile, Makefile) are
    -# intentionally excluded from MEDIA_TAG_CLEANUP_RE — they are validated and
    -# delivered via MEDIA_EXTENSIONLESS_TAG_RE so prompt-injection paths that do
    -# not exist on disk are left visible instead of silently dropped. Paths with
    -# an unknown but present extension (e.g. .weirdext) stay on the #34517
    -# bare-path fallback and are not handled here.
    +# Paths NOT covered by MEDIA_TAG_CLEANUP_RE's extension alternation — both
    +# extension-less files (Caddyfile, Dockerfile, Makefile) and files with an
    +# unknown extension (.py, .log, .weirdext, ...) — are validated and delivered
    +# via MEDIA_EXTENSIONLESS_TAG_RE. Every ``MEDIA:`` path is therefore
    +# deliverable regardless of file type (#36060): known extensions extract
    +# unconditionally via the anchored pattern above, everything else extracts
    +# only after ``validate_media_delivery_path`` accepts it (exists on disk, not
    +# under the credential/system denylist, strict-mode rules honored), so
    +# prompt-injection paths that do not validate are left visible instead of
    +# silently dropped.
     MEDIA_EXTENSIONLESS_TAG_RE = re.compile(
         r'''[`"']?MEDIA:\s*'''
         r'''(?P<path>`[^`\n]+`|"[^"\n]+"|'[^'\n]+'|'''
    ```

    ```diff
    @@ -1527,8 +1531,16 @@ def _normalize_media_tag_path(raw: str) -> str:
     
     
     def _path_lacks_deliverable_extension(path: str) -> bool:
    -    """True only when the basename has no extension (Caddyfile, Makefile, …)."""
    -    return not Path(path).suffix
    +    """True when MEDIA_TAG_CLEANUP_RE's extension alternation does not cover
    +    ``path`` — either the basename has no extension at all (Caddyfile,
    +    Makefile, …) or the extension is not in MEDIA_DELIVERY_EXTS (.py, .log,
    +    .weirdext, …). Such paths route through the validated delivery pass
    +    (``validate_media_delivery_path``) instead of the unconditional one, so
    +    every file type is deliverable (#36060) while nonexistent / denylisted
    +    paths stay visible in the text.
    +    """
    +    suffix = Path(path).suffix.lower()
    +    return not suffix or suffix not in MEDIA_DELIVERY_EXTS
     
     
     def _strip_media_tag_directives(text: str) -> str:
    ```
  - `tests/gateway/test_platform_base.py`

    ```diff
    @@ -631,12 +631,15 @@ def test_extract_media_and_local_files_share_one_extension_set(self):
     
         def test_unknown_extension_not_black_holed_by_cleanup(self):
             """A MEDIA: tag with an unknown extension is NOT stripped from the
    -        body — it survives so extract_local_files can still see the bare path,
    -        rather than vanishing entirely (the core of issue #34517)."""
    +        body by the extension-anchored cleanup — and when the path does not
    +        validate (nonexistent file here), it is not delivered either, so the
    +        tag survives visibly instead of vanishing (the core of issue #34517).
    +        Validated unknown-extension paths DO deliver — see
    +        TestUniversalMediaEgress (#36060)."""
             from gateway.platforms.base import MEDIA_TAG_CLEANUP_RE
             text = "Saved to MEDIA:/tmp/data.weirdext done"
             media, _ = BasePlatformAdapter.extract_media(text)
    -        assert media == []  # unknown extension is not a deliverable MEDIA tag
    +        assert media == []  # nonexistent path fails validation, not delivered
             stripped = MEDIA_TAG_CLEANUP_RE.sub("", text)
             assert "/tmp/data.weirdext" in stripped  # path preserved, not dropped
     
    ```

    ```diff
    @@ -712,6 +715,104 @@ def test_as_document_directive_stripped_without_media_tag(self):
             )
     
     
    +class TestUniversalMediaEgress:
    +    """#36060: every MEDIA: path is deliverable regardless of file type.
    +
    +    Known extensions extract unconditionally (MEDIA_TAG_CLEANUP_RE); unknown
    +    extensions and extension-less files extract via the validated pass —
    +    delivered when validate_media_delivery_path accepts them, left visible
    +    when it does not (nonexistent, denylisted).
    +    """
    +
    +    def _patch_allow_root(self, monkeypatch, root):
    +        monkeypatch.setattr(
    +            "gateway.platforms.base.MEDIA_DELIVERY_SAFE_ROOTS",
    +            (str(root),),
    +        )
    +        monkeypatch.delenv("HERMES_MEDIA_DELIVERY_STRICT", raising=False)
    +
    +    @pytest.mark.parametrize("name", [
    +        "script.py", "server.log", "notes.weirdext", "app.ts", "run.sh",
    +        "config.toml", "styles.css", "contract.sol",
    +    ])
    +    def test_unknown_extension_delivered_when_file_validates(
    +        self, tmp_path, monkeypatch, name,
    +    ):
    +        root = tmp_path / "output"
    +        root.mkdir()
    +        f = root / name
    +        f.write_text("content", encoding="utf-8")
    +        self._patch_allow_root(monkeypatch, root)
    +
    +        content = f"Here you go:\nMEDIA:{f}\nDone."
    +        media, cleaned = BasePlatformAdapter.extract_media(content)
    +        assert len(media) == 1
    +        assert media[0][0] == str(f.resolve())
    +        assert "MEDIA:" not in cleaned
    +        assert "Done." in cleaned
    +
    +    def test_unknown_extension_left_visible_when_not_on_disk(
    +        self, tmp_path, monkeypatch,
    +    ):
    +        root = tmp_path / "output"
    +        root.mkdir()
    +        self._patch_allow_root(monkeypatch, root)
    +
    +        content = "MEDIA:/nonexistent/script.py"
    +        media, cleaned = BasePlatformAdapter.extract_media(content)
    +        assert media == []
    +        assert "MEDIA:/nonexistent/script.py" in cleaned
    +
    +    def test_denylisted_paths_still_rejected_regardless_of_extension(
    +        self, tmp_path, monkeypatch,
    +    ):
    +        # A denylisted path must not deliver even though .py/.log/etc now
    +        # route through the validated pass. _media_delivery_denied_paths()
    +        # reads _MEDIA_DELIVERY_DENIED_PREFIXES at call time, so patching the
    +        # tuple exercises the real denylist logic.
    +        secret_dir = [redacted:secret-assignment] / "secrets"
    +        secret_dir.mkdir()
    +        f = secret_dir / "creds.py"
    +        f.write_text("TOKEN = 'x'", encoding="utf-8")
    +        monkeypatch.setattr(
    +            "gateway.platforms.base._MEDIA_DELIVERY_DENIED_PREFIXES",
    +            (str(secret_dir),),
    +        )
    +        monkeypatch.delenv("HERMES_MEDIA_DELIVERY_STRICT", raising=False)
    +
    +        content = f"MEDIA:{f}"
    +        media, cleaned = BasePlatformAdapter.extract_media(content)
    +        assert media == []
    +        assert "MEDIA:" in cleaned  # rejected tag stays visible
    +
    +    def test_strip_for_display_strips_validated_unknown_extension(
    +        self, tmp_path, monkeypatch,
    +    ):
    +        root = tmp_path / "output"
    +        root.mkdir()
    +        f = root / "server.log"
    +        f.write_text("x", encoding="utf-8")
    +        self._patch_allow_root(monkeypatch, root)
    +
    +        text = f"MEDIA:{f}"
    +        stripped = BasePlatformAdapter.strip_media_directives_for_display(text)
    +        assert "MEDIA:" not in stripped
    +
    +    def test_strip_for_display_keeps_unvalidated_unknown_extension(self):
    +        text = "MEDIA:/nonexistent/server.log"
    +        stripped = BasePlatformAdapter.strip_media_directives_for_display(text)
    +        assert "MEDIA:/nonexistent/server.log" in stripped
    +
    +    def test_known_extension_still_unconditional(self):
    +        # Known extensions keep the pre-#36060 behavior: extracted (and the
    +        # tag stripped) even when the file does not exist — downstream
    +        # delivery surfaces the failure.
    +        content = "MEDIA:/nonexistent/report.pdf"
    +        media, cleaned = BasePlatformAdapter.extract_media(content)
    +        assert media == [("/nonexistent/report.pdf", False)]
    +        assert "MEDIA:" not in cleaned
    +
    +
     class TestMediaDeliveryPathValidation:
         def _patch_roots(self, monkeypatch, *roots):
             monkeypatch.setattr(
    ```
- **#53354: fix(gateway): accept .stl and .3mf document uploads**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `4314d451ca961cb50c3430197a3a2c7a8575fd0e`
- **#60069: fix(gateway): allow .py/.markdown/.stl/.3mf media & document attachments**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#65510`

## Fix status

- Merged to main in PR #65510 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-16`.
- Subsystem: `gateway`
