{"repo":"NousResearch/hermes-agent","public_id":"7905","display_label":"is_local_endpoint detection of Docker/Podman and local hostnames","summary":"Members concern the agent's is_local_endpoint check not recognizing DNS-based local endpoints, such as Docker/Podman DNS names, mDNS/.lan names, and unqualified hostnames, with a related stale timeout affecting local LLM proxies. Several items are fixes proposed for this theme, and one is a bug report saying a recent fix did not cover DNS for local providers.","subsystem":"agent","counts":{"members":7,"open_issues":1,"open_issues_distinct":1,"open_prs":1,"closed_issues":1,"merged_prs":2,"attempt_prs":3},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":9248,"merged_at":"2026-06-05T00:18:10+00:00"},"resolution":{"state":"partially_resolved","by":"pr-9248"},"freshness":{"state":"verified","last_verified_at":"2026-06-05T00:18:10+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.25523809523809526},"fix_edges":[{"issue_number":7905,"pr_number":7950,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-04-11T21:46:18+00:00"},{"issue_number":7905,"pr_number":9248,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-05T00:18:10+00:00"}],"members":[{"kind":"issue","number":7905,"title":"is_local_endpoint misses Docker/Podman DNS names — stale timeout fires on local LLM proxies","url":null,"state":"closed"},{"kind":"issue","number":20346,"title":"[Bug]: using DNS for local provider is not addressed in recent fix","url":null,"state":"open"},{"kind":"pr","number":7906,"title":"fix: is_local_endpoint misses Docker/Podman DNS names","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#7950","number":7950},{"kind":"pr","value":"#9248","number":9248}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":7950,"title":"fix: is_local_endpoint misses Docker/Podman DNS names","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/model_metadata.py","tests/agent/test_local_stream_timeout.py","tests/tools/test_tool_result_storage.py","tools/tool_result_storage.py"],"fix_hunks":[{"file":"agent/model_metadata.py","hunks":["@@ -179,6 +179,12 @@ def _strip_provider_prefix(model: str) -> str:\n \n # Local server hostnames / address patterns\n _LOCAL_HOSTS = (\"localhost\", \"127.0.0.1\", \"::1\", \"0.0.0.0\")\n+# Docker / Podman / Lima DNS names that resolve to the host machine\n+_CONTAINER_LOCAL_SUFFIXES = (\n+    \".docker.internal\",\n+    \".containers.internal\",\n+    \".lima.internal\",\n+)\n \n \n def _normalize_base_url(base_url: str) -> str:","@@ -254,6 +260,9 @@ def is_local_endpoint(base_url: str) -> bool:\n         return False\n     if host in _LOCAL_HOSTS:\n         return True\n+    # Docker / Podman / Lima internal DNS names (e.g. host.docker.internal)\n+    if any(host.endswith(suffix) for suffix in _CONTAINER_LOCAL_SUFFIXES):\n+        return True\n     # RFC-1918 private ranges and link-local\n     import ipaddress\n     try:"]},{"file":"tests/agent/test_local_stream_timeout.py","hunks":["@@ -22,6 +22,9 @@ class TestLocalStreamReadTimeout:\n         \"http://0.0.0.0:5000\",\n         \"http://192.168.1.100:8000\",\n         \"http://10.0.0.5:1234\",\n+        \"http://host.docker.internal:11434\",\n+        \"http://host.containers.internal:11434\",\n+        \"http://host.lima.internal:11434\",\n     ])\n     def test_local_endpoint_bumps_read_timeout(self, base_url):\n         \"\"\"Local endpoint + default timeout -> bumps to base_timeout.\"\"\"","@@ -68,3 +71,38 @@ def test_empty_base_url_keeps_default(self):\n             if _stream_read_timeout == 120.0 and base_url and is_local_endpoint(base_url):\n                 _stream_read_timeout = _base_timeout\n             assert _stream_read_timeout == 120.0\n+\n+\n+class TestIsLocalEndpoint:\n+    \"\"\"Direct unit tests for is_local_endpoint.\"\"\"\n+\n+    @pytest.mark.parametrize(\"url\", [\n+        \"http://localhost:11434\",\n+        \"http://127.0.0.1:8080\",\n+        \"http://0.0.0.0:5000\",\n+        \"http://[::1]:11434\",\n+        \"http://192.168.1.100:8000\",\n+        \"http://10.0.0.5:1234\",\n+        \"http://172.17.0.1:11434\",\n+    ])\n+    def test_classic_local_addresses(self, url):\n+        assert is_local_endpoint(url) is True\n+\n+    @pytest.mark.parametrize(\"url\", [\n+        \"http://host.docker.internal:11434\",\n+        \"http://host.docker.internal:8080/v1\",\n+        \"http://gateway.docker.internal:11434\",\n+        \"http://host.containers.internal:11434\",\n+        \"http://host.lima.internal:11434\",\n+    ])\n+    def test_container_dns_names(self, url):\n+        assert is_local_endpoint(url) is True\n+\n+    @pytest.mark.parametrize(\"url\", [\n+        \"https://api.openai.com\",\n+        \"https://openrouter.ai/api\",\n+        \"https://api.anthropic.com\",\n+        \"https://evil.docker.internal.example.com\",\n+    ])\n+    def test_remote_endpoints(self, url):\n+        assert is_local_endpoint(url) is False"]},{"file":"tests/tools/test_tool_result_storage.py","hunks":["@@ -124,6 +124,34 @@ def test_uses_parent_dir_of_remote_path(self):\n         cmd = env.execute.call_args[0][0]\n         assert \"mkdir -p /data/data/com.termux/files/usr/tmp/hermes-results\" in cmd\n \n+    def test_path_with_spaces_is_quoted(self):\n+        env = MagicMock()\n+        env.execute.return_value = {\"output\": \"\", \"returncode\": 0}\n+        remote_path = \"/tmp/hermes results/abc file.txt\"\n+        _write_to_sandbox(\"content\", remote_path, env)\n+        cmd = env.execute.call_args[0][0]\n+        assert \"'/tmp/hermes results'\" in cmd\n+        assert \"'/tmp/hermes results/abc file.txt'\" in cmd\n+\n+    def test_shell_metacharacters_neutralized(self):\n+        \"\"\"Paths with shell metacharacters must be quoted to prevent injection.\"\"\"\n+        env = MagicMock()\n+        env.execute.return_value = {\"output\": \"\", \"returncode\": 0}\n+        malicious_path = \"/tmp/hermes-results/$(whoami).txt\"\n+        _write_to_sandbox(\"content\", malicious_path, env)\n+        cmd = env.execute.call_args[0][0]\n+        # The $() must not appear unquoted — shlex.quote wraps it\n+        assert \"'/tmp/hermes-results/$(whoami).txt'\" in cmd\n+\n+    def test_semicolon_injection_neutralized(self):\n+        env = MagicMock()\n+        env.execute.return_value = {\"output\": \"\", \"returncode\": 0}\n+        malicious_path = \"/tmp/x; rm -rf /; echo .txt\"\n+        _write_to_sandbox(\"content\", malicious_path, env)\n+        cmd = env.execute.call_args[0][0]\n+        # The semicolons must be inside quotes, not acting as command separators\n+        assert \"'/tmp/x; rm -rf /; echo .txt'\" in cmd\n+\n \n class TestResolveStorageDir:\n     def test_defaults_to_storage_dir_without_env(self):"]},{"file":"tools/tool_result_storage.py","hunks":["@@ -24,6 +24,7 @@\n \n import logging\n import os\n+import shlex\n import uuid\n \n from tools.budget_config import (","@@ -79,7 +80,7 @@ def _write_to_sandbox(content: str, remote_path: str, env) -> bool:\n     marker = _heredoc_marker(content)\n     storage_dir = os.path.dirname(remote_path)\n     cmd = (\n-        f\"mkdir -p {storage_dir} && cat > {remote_path} << '{marker}'\\n\"\n+        f\"mkdir -p {shlex.quote(storage_dir)} && cat > {shlex.quote(remote_path)} << '{marker}'\\n\"\n         f\"{content}\\n\"\n         f\"{marker}\"\n     )\n"]}]},{"kind":"pr","number":8021,"title":"fix(agent): detect Docker/Podman DNS names in is_local_endpoint","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#7906","number":7906}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":9248,"title":"fix(local): recognize unqualified hostnames as local endpoints","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/model_metadata.py","tests/agent/test_local_stream_timeout.py"]},{"kind":"pr","number":20359,"title":"fix(agent): treat mDNS/.lan/unqualified hostnames as local endpoints (#20346)","url":null,"state":"open","close_reason":{"refs":[{"kind":"pr","value":"#9248","number":9248}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}